Android自定義StepView指示器
按照規矩先上圖
問:為什麼寫這個自定義View呢?
答:工作讓我成長。
Android關於自定義View的文章不再新鮮,實現方式也是各有千秋,在這裡自己寫的一個進度指示器實現方式是通過繼承自View
實現,內部所有的圓圈、線條和文字都是直接繪製,沒有以ViewGroup
的addView()
方式去做。
1.理清思路
以最終效果圖為參考,我們要分4步走
- 繪製空心的圓圈
- 繪製實心的圓
- 繪製圓圈之間的線條
- 繪製每一個步驟的說明文字
2.準備工作
根據需求來,哪些東西是可以使用者自定義的,是通過屬性控制還是暴露setter
出去自己決定。這裡我準備的attribute
xml
<declare-styleable name="ProgressView">
<attr name="defaultColor" format="color"/>
<attr name="reachedColor" format="color"/>
<attr name="max" format="integer"/>
<attr name="progress" format="integer"/>
<!--圓圈和線條之間的間隙-->
<attr name="gap" format="dimension"/>
<!--文字說明和圓圈之間的margin-->
<attr name="textMargin" format="dimension"/>
<attr name="lineHeight" format="dimension"/>
<attr name="lineColor" format="color"/>
<attr name="minLineWidth" format="dimension"/>
<!--圓點各項資料-->
<attr name="centerRadius" format="dimension"/>
<attr name="strokeWidth" format="dimension"/>
<attr name="strokeRadius" format="dimension"/>
<!--文案-->
<attr name="contentText" format="string"/>
<attr name="stepTextSize" format="dimension"/>
<attr name="contextTextSize" format="dimension"/>
<attr name="contextTextColor" format="color"/>
<!--文字的位置-->
<attr name="indicator_mode" format="enum">
<enum name="bottom" value="1"/>
<enum name="top" value="2"/>
</attr>
</declare-styleable>
然後是在
View
中定義的一些變數
private Paint mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);//空心圓圈畫筆
private Paint mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//實心圓圈畫筆
private Paint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);//文字畫筆
private Paint mLinePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);//線條畫筆
private RectF mLineRect = new RectF();//線條
private Rect mTextRect = new Rect();//進度文字
private float mGap;//線條與圓圈之間的間隔
private float mMargin;//文字與圓圈垂直方向的間距
private float mStepTextSize;//進度文字大小
private float mLineHeight;//線條高度
private float mStrokeWidth;//外圈的寬度
private float mCenterRadius;//內圈半徑
private float mStrokeRadius;//外圈半徑
private float mCircleHeight;//繪製圓圈的高度
private float mTextY;//繪製文案的Y值
private int mMode;//文字相對於圓圈的位置,上下
private int mProgress;
private int mMaxCount;
private int mDefaultColor;
private int mReachedColor;
private int mTextColor;
private int mLineColor;
private int mWidth;
private int mHeight;
3.開始寫程式碼
初始化的自定義View程式碼是需要套公式的,在構造中通過TypedArray
獲取View的各種屬性值、為一些屬性提供預設值、定義好屬性變數、根據View的需要提供預設的寬高等,然後繼續。
3.1重寫onMeasure()
在onMeasure
方法中一定要為你的View新增好寬高限制,關鍵點是View的測量要避免在MeasureSpec.AT_MOST
和MeasureSpec.UNSPECIFIED
的情況下獲取不到想要的效果,最好提供給一個預設值。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//measure()方法為自己定義的測量方法
setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
}
private int measure(int measureSpec, boolean isWidth) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();
result += padding;
if (mode == MeasureSpec.AT_MOST) {
if (isWidth) {
result = Math.max(result, size);
} else {
result = Math.min(result, size);
}
}
}
return result;
}
這裡給View提供的最小寬高分別是一個圓圈的寬和一個200的高。
@Override
protected int getSuggestedMinimumWidth() {
return (int) (mStrokeRadius * 2);
}
@Override
protected int getSuggestedMinimumHeight() {
return DEFAULT_HEIGHT;
}
3.2onDraw()
繪製
到現在為止我們已經控制好View的寬高了,其實也可以先繪製一番看看效果了,接著在onDraw
裡面根據最大的mMaxCount和mProgress繪製圓圈。
- (這裡View繪製的效果是居中顯示的)首先考慮繪製最外層的空心圓圈,如果
mMaxCount
為1的時候圓圈的位置就是螢幕的中心點了,如果mMaxCount
為2的時候呢?我的想法是把螢幕可用的寬度(除去padding)2等分,兩個圓點之間的寬度權重佔一,兩點距離左右的邊距各自權重為0.5,以此類推。- 繪製實心圓圈就是和空心圓的位置完全一樣,只需要傳入不同的畫筆和半徑就OK了。
- 最後繪製圓圈之間的連線線,兩個圓圈之間的間隔
g
的長度是可以計算出來的,然後在這個基礎上減去圓圈的半徑就OK了,當然友好一點的做法還是要通過一個屬性gap
去控制線條與圓圈之間的margin
值。- 為了讓線條的高度可以自定義,這裡的線條就通過
Rect
去繪製了。
int height = getHeight() - getPaddingBottom() - getPaddingTop();
int usefulWidth = mWidth - getPaddingLeft() - getPaddingRight();//可用寬度
float g = (usefulWidth / (mMaxCount));
//起始點
float startX = getPaddingLeft() + mStrokeRadius;
startX += g / 2.0f;//這裡繪製不能超出paddingRight
for (int iLoop = 0; iLoop < mMaxCount; iLoop++) {
float drawX = startX + (iLoop * g);
//已完成進度的色值
mStrokePaint.setColor(iLoop <= mProgress - 1 ? mReachedColor : mDefaultColor);
canvas.drawCircle(drawX, height / 2, mStrokeRadius, mStrokePaint);
//繪製進度位置
if (iLoop <= mProgress - 1) {
canvas.drawCircle(drawX, height / 2, mCenterRadius, mCenterPaint);
}
}
//繪製連線線
if (mDrawLine) {//mMaxCount大於1的時候就繪製連線線
int lineCount = mMaxCount - 1;
for (int lLoop = 0; lLoop < lineCount; lLoop++) {
float drawXStart = startX + mStrokeRadius + dp2px(mGap) + (lLoop * g);
float drawXEnd = startX + g - mStrokeRadius - dp2px(mGap) + (lLoop * g);
mLineRect.left = drawXStart;
mLineRect.right = drawXEnd;
canvas.drawRect(mLineRect, mLinePaint);
}
}
看下效果,初步就長這個樣子,當然具體的各個值是要經過計算和除錯的。
接下來就要繪製文字了,文字的繪製位置需要謹慎操作,每一個步驟圓心的位置也就是你每一步文案說明的X軸中心,而Y值需要另行計算,以圓圈在文案上方為例子,圓圈的最底部和文案的最上方應該是Y值相等的,但是首先我們需要保證的是圓圈和文字的繪製位置Y軸一致,然後再去做加減法控制文字的位移,這裡控制文字的Y軸是不能通過簡單的textSize
控制的,關於Paint
的decent
等方法文末附上鍊接,於是有了下面這段程式碼控制繪製文案的Y軸。
private void calcSize() {
int usefulHeight = mHeight - getPaddingBottom() - getPaddingTop();
if (checkSteps()) {
mDrawText = true;
mTextPaint.getTextBounds(String.valueOf(mSteps.get(0)), 0, String.valueOf(mSteps.get(0)).length(), mTextRect);
float center = (mTextPaint.ascent() + mTextPaint.descent()) / 2.0f;
if (mMode == MODE_BOTTOM) {
mCircleHeight = usefulHeight / 2.0f - mTextSize;
mTextY = mCircleHeight - center;
} else {
mCircleHeight = usefulHeight / 2.0f + mTextSize;
mTextY = mCircleHeight - center;
}
} else {
mDrawText = false;
mCircleHeight = usefulHeight / 2.0f;
}
......
}
OK,現在文字和圓圈的Y軸一致了,然後我們根據mode
來控制文字上下方的位移,如果文字在圓圈上方,那麼mTextY
的值就需要在原來的基礎上減去圓圈半徑mStrokeRadius
,然後再減去mTextSize
的1/2才能正好使得文字的最下方和圓圈的正上方是同一個Y軸。看程式碼。
......
if (checkSteps()) {
mDrawText = true;
mTextPaint.getTextBounds(String.valueOf(mSteps.get(0)), 0, String.valueOf(mSteps.get(0)).length(), mTextRect);//這裡假設所有的文案字型大小是一樣的,忽略SpannableString帶來的影響,嚴謹一點的做法可以在onDraw()裡自行測量
float center = (mTextPaint.ascent() + mTextPaint.descent()) / 2.0f;
if (mMode == MODE_BOTTOM) {
mCircleHeight = usefulHeight / 2.0f - mTextSize;
mTextY = mCircleHeight - center + mStrokeRadius + mTextSize / 2.0f + dp2px(mMargin);
} else {
mCircleHeight = usefulHeight / 2.0f + mTextSize;
mTextY = mCircleHeight - center - mStrokeRadius - mTextSize / 2.0f - dp2px(mMargin);
}
}
然後再來看一下效果
貌似文案的繪製也OK了,接下來就需要修飾一番了。
最後,我加了textMargin
屬性控制文案和圓圈之間的間隔,indicator_mode
屬性來控制文案和圓圈的相對位置.最終的結果就是效果圖了,程式碼連結附上。
相關推薦
Android自定義StepView指示器
按照規矩先上圖 問:為什麼寫這個自定義View呢? 答:工作讓我成長。 Android關於自定義View的文章不再新鮮,實現方式也是各有千秋,在這裡自己寫的一個進度指示器實現方式是通過繼承自View實現,內部所有的圓圈、線條和文字都是直接
Android自定義ViewPager指示器
自我感覺ViewPager+fragment在Android中挺重要的,自己還不太明白 ViewPagerIndicator的思路: 先在佈局最上面載入一張黑色圖片,作為標籤的背景 然後將剩餘部分全都置為VIewPager,用來滑動。高度為0dp,用lay
Android---自定義ViewPager指示器(一)
最近學習了怎麼自己畫Viewpager指示器的形狀。 新建立一個ViewPagerIndicator類 繼承自LinearLayout 初始化Paint例項 在它的含有兩個引數的構造方法內初始化要畫的圖形所需要的工具,例如:我們要畫一個三角形。首先我
Android自定義Viewpager指示器PagerIndicator-仿微博頭條效果
平時工作之餘,喜歡看看新聞,手機難免會裝了幾個新聞閱讀類的app。新聞類的app風格大致一致,可以選擇不同欄目,欄目可以切換。最近就在用微博頭條,感覺介面挺清新的。而且它使用的PagerIndicator挺好看的。昨晚居然準時下班了,趁著早就實現了下。今天
Android -- 自定義view之StepView
先看看實現的效果: 2,首先我們來看看我們常規的自定義view的基礎步驟吧 1,繼承View,重寫構造方法 2,自定義屬性 3,重寫onMeasure()測量控制元件高度 4,重寫onDra
Android UI之自定義頭部指示器
1. 簡介 本文將記錄的是一些有效的自定義指示器demo,誠然Indicator的開源框架還是比較多的,我們在具體具體中可以衡量。一些簡單的自定義基礎還是有必要好好掌握的。 2. demo樣例 2.1 圖片背景選擇+ViewPager樣式 首先看下效果吧: 如下圖
Android中自定義TabLayout指示器長度
效果圖: MainActivity.java檔案 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullabl
Android自定義View完美實現指示器位置隨進度變化的IndicateProgressView
該文章同步釋出在公眾號”LinminTech”上,請在本文最後掃碼關注,獲取更多精彩Android開發文章。 效果圖 需求 在平時開發過程中,UI經常要求實現如上圖所示的ProgressBar,但是Android系統自帶的ProgressBar
Android從零開搞系列:自定義View(4)基本的自定義ViewPager指示器+開源專案分析(上)
基本的自定義ViewPager的指示器 當然關於ViewPager指示器,如果只需要簡潔大方,那麼我們最簡單的方案就是使用TabLayout+ViewPager。 當然咱們也有很多非常不錯的開源框架可以選擇。 本次的記錄的內容就是
Android 自定義控件之繼承ViewGroup創建新容器
多個 osi count() arc ron min ole tro 定位 歡迎大家來學習本節內容,前幾節我們已經學習了其他幾種自定義控件,分別是Andriod 自定義控件之音頻條及 Andriod 自定義控件之創建可以復用的組合控件還沒有學習的同學請先去學習下,因為本節將
Android自定義相機超詳細講解
了解 catch 實現 4.4 required form 需要 eset 自己 Android自定義相機超詳細講解 轉載請標明出處: http://blog.csdn.net/vinicolor/article/details/49642861; 由於網上關於Andr
Android 自定義View
wid declare created odi lex getwidth 實現 tdi led 最近在看鴻洋大神的博客,在看到自定義部分View部分時,想到之前案子中經常會要用到"圖片 + 文字"這類控件,如下圖所示: 之前的做法是在布局文件中,將一個Imag
Android 自定義控件之 日期選擇控件
gin pri 選中 att files ger bottom null count() 效果如下: 調用的代碼: @OnClick(R.id.btn0) public void btn0() { final AlertDialog dialog
android 自定義控件之簡單的loading框
void stroke color mat pri htm img 溫習 時機 好吧,久不動android,感覺自己已經快是條鹹魚了,趁著這周的開發任務已完成,下周的開發計劃未下來之際,來溫習一下android的自定義控件,於是就有了下面這個醜陋的玩意 實現起
aNDROID自定義進度條顏色
定義 hao123 自定義進度條 com list andro 顏色 androi baidu DIaLOGFRaGMENT%E9%97%AE%E9%A2%98%E6%B1%82%E8%A7%A3 http://music.baidu.com/songlist/49564
aNDROID自定義長點擊事件
list android aid 點擊事件 .com baidu andro oid lis %E6%8E%A8%E8%8D%90%E6%9A%82%E6%97%B6%E4%B8%8D%E8%A6%81%E5%8D%87%E7%BA%A7aDT22 http://musi
Android自定義view詳解
this boolean mar 處理 都是 並且 jdk text 命名 從繼承開始 懂點面向對象語言知識的都知道:封裝,繼承和多態,這是面向對象的三個基本特征,所以在自定義View的時候,最簡單的方法就是繼承現有的View 通過上面這段代碼,我定義了一個Ske
Android -- 自定義view實現keep歡迎頁倒計時效果
super onfinish -m use new getc awt ttr alt 1,最近打開keep的app的時候,發現它的歡迎頁面的倒計時效果還不錯,所以打算自己來寫寫,然後就有了這篇文章。 2,還是老規矩,先看一下我們今天實現的效果 相較於我們常見的倒計時
Android自定義View效果目錄
class 重寫 自定義 textview 居中 url 冒泡 and 雷達圖 1、絢麗的loading動效的實現 2、Android自定義View:進度條+冒泡文本 3、Android雷達圖(蜘蛛網圖) 4、Android文本閃爍 5、Android繪制圓形進度條 6、重
Android自定義View——實現水波紋效果類似剩余流量球
string 三個點 pre ber block span 初始化 move 理解 最近突然手癢就想搞個貝塞爾曲線做個水波紋效果玩玩,終於功夫不負有心人最後實現了想要的效果,一起來看下吧: 效果圖鎮樓 一:先一步一步來分解一下實現的過程 需要繪制一個正弦曲線(sin