首先看一下最后實現(xiàn)的效果圖
可左右滑動驾中,點擊加減按鈕動態(tài)改變數(shù)值和位置顯示玲躯。
思路:
1.根據(jù)圖上UI知道有三種狀態(tài)
<li>未滑中區(qū)域的灰色底部狀態(tài)</li>
<li>滑中的顏色改變狀態(tài)</li>
<li>橢圓的邊框顏色,和內(nèi)部字體顯示</li>
實現(xiàn):
首先我們自定義一個類MySeekBar繼承View减拭。并實現(xiàn)最重要的onDraw澜沟,onMeasure方法。
1.首先定義一個樣式峡谊,用來以后設置底部顏色,滑中顏色,字體大小既们,顏色等
資源樣式
<declare-styleable name="MySeekBar">
<attr name="mTopBarColor" format="color"></attr> 滑動時候橫條顏色
<attr name="mBottomBarColor" format="color"></attr>默認的橫條顏色
<attr name="strokeColor" format="color"></attr>橢圓邊框顏色
<attr name="month" format="boolean"></attr>判斷是按年份顯示還是月份顯示
<attr name="mytext" format="string"></attr>獲得textview顯示的后綴
</declare-styleable>
2.實現(xiàn)MySeekBar構造器濒析。初始化資源,與需要的畫筆啥纸。(注意獲得資源后一定要使用recycle()号杏,去進行回收)
public MySeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attr = getTypedArray(context, attrs, R.styleable.MySeekBar);
mTopBarColor = attr.getColor(R.styleable.MySeekBar_mTopBarColor, getResources().getColor(R.color.color_EB7046));//設置滑動時候橫條顏色
mBottomBarColor = attr.getColor(R.styleable.MySeekBar_mBottomBarColor, getResources().getColor(R.color.login_font));//默認的橫條顏色
month = attr.getBoolean(R.styleable.MySeekBar_month, false);//判斷是按年份顯示還是月份顯示
myText = attr.getString(R.styleable.MySeekBar_mytext);//獲得textview顯示的后綴
mContext = context;
Data(Calendar.getInstance());//得到當前的日期
windows_width = DisplayMetricsTool.getWidth(mContext);//獲得屏幕寬度用來適配各種屏幕
dip_size = DisplayMetricsTool.dip2px(mContext, 75);
//矩形的白色底部畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint = new Paint(); //設置一個筆刷大小是3的黃色的畫筆
paint.setStyle(Paint.Style.FILL);//充滿
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);// 設置畫筆的鋸齒效果
//矩形邊框的的畫筆
whit = new Paint(); //設置一個筆刷大小是3的黃色的畫筆
whit.setStyle(Paint.Style.STROKE);
whit.setColor(mTopBarColor);
whit.setStrokeWidth(1);
whit.setAntiAlias(true);// 設置畫筆的鋸齒效果
//設置字體
textPaint = new Paint();
textPaint.setColor(mTopBarColor);
textPaint.setTextSize(DisplayMetricsTool.canvasTextSize(mContext));
textPaint.setStyle(Paint.Style.FILL);
//該方法即為設置基線上那個點究竟是left,center,還是right 這里我設置為center
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setAntiAlias(true);
attr.recycle();
}
3.自定義一個方法設置進度值,默認滿進度100.
/**
* 頂部進度條初始百分比進度
*/
private float mOriginalPercent = 0f;
/**
* 設置初始進度值斯棒,默認進度最大值為100
*
* @param progress
*/
public void setOriginalProgress(int progress) {
if (month) {//按年顯示
progress = 100 - (year - progress);//總進度減去今年和傳入年份的差值
} else {
//按月顯示
progress = (int) (progress * 8.4);//12月占100的百分比8.4
}
mOriginalPercent = progress / 100f;
if (mOriginalPercent < 0) {
mOriginalPercent = 0.0f;
} else if (mOriginalPercent > 1.0f) {
mOriginalPercent = 1.0f;
}
}
4.需要在onMeasure方法中計算盾致,當前傳入的進度換算之后占在屏幕的哪個位置。
//獲得密度轉像素的大小
private int dip_size;
// view的寬
private int mViewWidth;
// view的高
private int mViewHeight;
// 底部進度條的寬
private int mBottomBarWidth;
//底部進度條左邊位置
private int mBottomBarLeft;
// 底部進度條上邊位置
private int mBottomBarTop;
// 底部進度條右邊位置
private int mBottomBarRight;
// 底部進度條底邊位置
private int mBottomBarBottom;
// 頂部進度條的右邊位置
private int mTopBarRight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST) {
mViewWidth = mTvWidth * 4;
} else if (widthMode == MeasureSpec.EXACTLY) {
if (mTvWidth * 4 >= widthSize) {
mViewWidth = mTvWidth * 4;
} else {
mViewWidth = widthSize;
}
}
widthMeasureSpec = MeasureSpec.makeMeasureSpec(mViewWidth, MeasureSpec.EXACTLY);
if (heightMode == MeasureSpec.AT_MOST) {
mViewHeight = mTvHeight + 4 * mPadding;
} else if (heightMode == MeasureSpec.EXACTLY) {
if (heightSize <= mTvHeight + 4 * mPadding) {
mViewHeight = mTvHeight + 4 * mPadding;
} else {
mViewHeight = heightSize;
}
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(mViewHeight, MeasureSpec.EXACTLY);
mBottomBarWidth = mViewWidth - 2 * mTvWidth - 6 * mPadding;
mBottomBarLeft = mTvWidth + 3 * mPadding;
mBottomBarRight = mViewWidth - mTvWidth - 3 * mPadding;
mBottomBarBottom = (mViewHeight - mTvHeight) / 2 + mTvHeight;
mBottomBarTop = mBottomBarBottom - mBarHeight;
mCircleY = mBottomBarBottom - mBarHeight / 2;//圓心的坐標
mPosition = (int) Math.round((mBottomBarWidth - DisplayMetricsTool.getWidth(mContext) / 6) * mOriginalPercent + 0.5) + mBottomBarLeft;
if (mPosition <= mBottomBarLeft) {
mPosition = mBottomBarLeft;
} else if (mPosition >= mBottomBarRight) {
mPosition = mBottomBarRight;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
5.因為需要左右滑動荣暮,所以我們采用GestureDetector的手勢滑動管理庭惜,判斷當用戶點擊在橢圓中才能讓滑動改變
GestureDetector mGestureDetector = new GestureDetector(mContext, new GestureDetector.OnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (mWhereClickedState == WHERR_CLICKED_CIRCLE) {
} else if (mWhereClickedState == WHERR_CLICKED_BAR) {
mPosition = (int) Math.round(e.getX() + 0.5);
if (mPosition >= mBottomBarRight) {
mPosition = mBottomBarRight;
} else if (mPosition <= mBottomBarLeft) {
mPosition = mBottomBarLeft;
}
MySeekBar.this.invalidate();
}
return false;
}
@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
/* if (mWhereClickedState == WHERR_CLICKED_CIRCLE) {*/
mPosition = (int) Math.round(e2.getX() + 0.5);
if (mPosition >= mBottomBarRight) {
mPosition = mBottomBarRight;
} else if (mPosition <= mBottomBarLeft) {
mPosition = mBottomBarLeft;
}
MySeekBar.this.invalidate();
/* } else if (mWhereClickedState == WHERR_CLICKED_BAR) {
}*/
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onDown(MotionEvent e) {
float event_x = e.getX();
float event_y = e.getY();
mWhereClickedState = judgeWhereClicked(event_x, event_y);
if (mWhereClickedState == WHERR_CLICKED_VIEW) {
return false;
} else {
return true;
}
}
});
/**
* 判斷用戶點擊位置狀態(tài)
*
* @param x 用戶點擊的x坐標
* @param y 用戶點擊的y坐標
* @return 返回用戶點擊未知狀態(tài):
* WHERR_CLICKED_CIRCLE
* WHERR_CLICKED_BAR
* WHERR_CLICKED_VIEW
*/
public int judgeWhereClicked(float x, float y) {
if (rectF.contains(x, y)) {
return WHERR_CLICKED_CIRCLE; //s_x和s_y界定的區(qū)域,即圓圈區(qū)域
} else {
return WHERR_CLICKED_VIEW; //view除了上述兩部分的部分
}
}
6.現(xiàn)在畫東西的筆有了穗酥,架子有了护赊,裝飾材料有了,只剩往一塊白布里面畫東西砾跃。就成了骏啰。 接下來最重要的就是在onDraw方法中,把UI畫上去抽高。
@Override
protected void onDraw(Canvas canvas) {
mTopBarRight = mPosition;
mCircleX = mTopBarRight;
mPaint.setColor(mBottomBarColor);
canvas.drawRect(mBottomBarLeft, mBottomBarTop, mBottomBarRight, mBottomBarBottom, mPaint);
mPaint.setColor(mTopBarColor);
canvas.drawRect(mBottomBarLeft, mBottomBarTop, mTopBarRight, mBottomBarBottom, mPaint);
if (mBottomBarRight - mTopBarRight > windows_width / 6 - 10) {//用來防止百分比是100的時候橢圓形畫出邊界的問題判耕。
aaa = mTopBarRight;
rectF = new RectF(mTopBarRight, dip_size / 2 - dip_size / 5, mTopBarRight + windows_width / 6, dip_size / 2 + dip_size / 5);
canvas.drawRoundRect(rectF,
(windows_width / 6) / 3, //x軸的半徑
(windows_width / 6) / 3 - 5, //y軸的半徑
paint);//用來畫矩形白色背景
canvas.drawRoundRect(rectF,
(windows_width / 6) / 3, //x軸的半徑
(windows_width / 6) / 3 - 5, //y軸的半徑
whit);//用來畫矩形邊框
} else {
rectF = new RectF(aaa, dip_size / 2 - dip_size / 5, aaa + windows_width / 6, dip_size / 2 + dip_size / 5);
canvas.drawRoundRect(rectF,
(windows_width / 6) / 3, //x軸的半徑
(windows_width / 6) / 3 - 5, //y軸的半徑
paint);//用來畫矩形白色背景
canvas.drawRoundRect(rectF,
(windows_width / 6) / 3, //x軸的半徑
(windows_width / 6) / 3 - 5, //y軸的半徑
whit);//用來畫矩形邊框
}
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float top = fontMetrics.top;//為基線到字體上邊框的距離,即上圖中的top
float bottom = fontMetrics.bottom;//為基線到字體下邊框的距離,即上圖中的bottom
int baseLineY = (int) (rectF.centerY() - top / 2 - bottom / 2);//基線中間點的y軸計算公式
canvas.drawText(getProgress() + myText, rectF.centerX(), baseLineY, textPaint);
}
7.獲得當前進度由于這個項目是按年或者月顯示的,所以獲取進度時和設置進度時都需要按照滿進度值100來進行換算翘骂,設置獲得進度的方法getProgress()壁熄。
/**
* 獲得當前進度值
*
* @return int
*/
public int getProgress() {
float percent = (mPosition - mBottomBarLeft) * 1.0f / (mBottomBarWidth -windows_width/ 6);
if (percent < 0.0) {
percent = 0f;
} else if (percent > 1) {
percent = 1f;
}
int progress = ((int) Math.round(percent * 100 + 0.5) - 1);
if (progress <= 0) {
progress = 1;
} else if (progress > 100) {
progress = 100;
}
if (month) {
return year - (100 - progress);
} else {
return progress / 8 == 0 ? 1 : progress / 8;
}
}
8.項目需要按左右按鈕的時候必要你動態(tài)設置進度值,并改變橢圓的位置雏胃,那么我們只需要改變進度值请毛,然后不斷去onDraw重新繪制界面就行了。代碼如下
/**
* 通過傳入進度值來更新進度條
*/
public void updatePositionFromProgress(int progress) {
if (month) {//按年顯示
progress = 100 - (year - progress);//總進度減去今年和傳入年份的差值
} else {
//按月顯示
progress = (int) (progress * 8.4);
}
float percent = progress / 100f;
if (percent < 0) {
percent = 0.0f;
} else if (percent > 1.0f) {
percent = 1.0f;
}
mPosition = (int) Math.round((mBottomBarWidth -windows_width / 6)* percent + 0.5) + mBottomBarLeft;
if (mPosition <= mBottomBarLeft) {
mPosition = mBottomBarLeft;
} else if (mPosition >= mBottomBarRight) {
mPosition = mBottomBarRight;
}
this.invalidate();
}
#######此時基本的實現(xiàn)已經(jīng)完成瞭亮。使用方式基本就是在XML設置顏色方仿,字體之類的事,然后動態(tài)去設置進度--->代碼如下
<ui.view.MySeekBar
android:id="@+id/seekbar"
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/right_ll"
android:layout_toRightOf="@+id/left_ll"
app:mTopBarColor="@color/color_EB7046"
app:month="true"
app:mytext="年" />
year = calendar.get(Calendar.YEAR);//當前的年
seekbar = (MySeekBar) view.findViewById(R.id.seekbar);
seekbar.setOriginalProgress(year);
至此整個功能差不多已經(jīng)完成统翩。剛開始的時候也是研究了網(wǎng)上有關的項目去進行研究仙蚜,然后自己重新更改封裝,弄成符合自己項目需要東西厂汗,希望能給有需要的同學一點思路委粉,不要想輪子能完全給你解決項目的難點,而是提供給你一個解決問題的思路,google娶桦,github贾节,爆棧等等汁汗,有難點的時候大家可以好好利用。
<li>下一篇準備寫自定義recyclerview的上拉加載功能栗涂,因為看了網(wǎng)上大多數(shù)的開源項目知牌,基本上都是華麗但不實用,功能太多真正能在項目用得上的很少斤程,增加了方法樹角寸,包的大小,所以將會寫一個只有上拉加載的recyclerview功能<li>