教你搞定Android自定義View

Android App開發(fā)過程中,很多時候會遇到系統(tǒng)框架中提供的控件無法滿足我們產(chǎn)品的設計需求鞋囊,那么這時候我們可以選擇先Google下有沒有比較成熟的開源項目可以讓我們用踪古,當然現(xiàn)在Github上面的項目非常豐富湘捎,能夠滿足我們絕不多數(shù)的開發(fā)需求,但是在使用這些炫酷的第三方控件時醒叁,我們也要想一想司浪,我們是不是也可以發(fā)揮自己的想象力,動手實現(xiàn)自己想要的控件把沼,盡可能掌控實現(xiàn)的細節(jié)啊易!

View

Android所有的控件都是View或者View的子類,它其實表示的就是屏幕上的一塊矩形區(qū)域饮睬,用一個Rect來表示租谈,left,top表示View相對于它的parent View的起點捆愁,width割去,height表示View自己的寬高,通過這4個字段就能確定View在屏幕上的位置昼丑,確定位置后就可以開始繪制View的內(nèi)容了呻逆。

View繪制過程

View的繪制可以分為下面三個過程:

  • Measure
    View會先做一次測量,算出自己需要占用多大的面積菩帝。View的Measure過程給我們暴露了一個接口onMeasure咖城,方法的定義是這樣的,

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}
    

View類已經(jīng)提供了一個基本的onMeasure實現(xiàn)呼奢,

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  }
  public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
  }

其中invoke了setMeasuredDimension()方法宜雀,設置了measure過程中View的寬高,getSuggestedMinimumWidth()返回View的最小Width握础,Height也有對應的方法辐董。插幾句,MeasureSpec類是View類的一個內(nèi)部靜態(tài)類禀综,它定義了三個常量UNSPECIFIED简烘、AT_MOST、EXACTLY定枷,其實我們可以這樣理解它孤澎,它們分別對應LayoutParams中match_parent、wrap_content依鸥、xxxdp亥至。我們可以重寫onMeasure來重新定義View的寬高悼沈。

  • Layout
    Layout過程對于View類非常簡單贱迟,同樣View給我們暴露了onLayout方法

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }
    

因為我們現(xiàn)在討論的是View姐扮,沒有子View需要排列,所以這一步其實我們不需要做額外的工作衣吠。插一句茶敏,對ViewGroup類,onLayout方法中缚俏,我們需要將所有子View的大小寬高設置好惊搏,這個我們下一篇會詳細說。

  • Draw
    Draw過程忧换,就是在canvas上畫出我們需要的View樣式恬惯。同樣View給我們暴露了onDraw方法

    protected void onDraw(Canvas canvas) {
    }
    

默認View類的onDraw沒有一行代碼,但是提供給我們了一張空白的畫布亚茬,舉個例子酪耳,就像一張畫卷一樣,我們就是畫家刹缝,能畫出什么樣的效果碗暗,完全取決我們。

View中還有三個比較重要的方法

  • requestLayout
    View重新調(diào)用一次layout過程梢夯。

  • invalidate
    View重新調(diào)用一次draw過程

  • forceLayout
    標識View在下一次重繪言疗,需要重新調(diào)用layout過程。

自定義屬性

整個View的繪制流程我們已經(jīng)介紹完了颂砸,還有一個很重要的知識噪奄,自定義控件屬性,我們都知道View已經(jīng)有一些基本的屬性沾凄,比如layout_width梗醇,layout_height,background等撒蟀,我們往往需要定義自己的屬性叙谨,那么具體可以這么做。

  • 1.在values文件夾下保屯,打開attrs.xml手负,其實這個文件名稱可以是任意的,寫在這里更規(guī)范一點姑尺,表示里面放的全是view的屬性竟终。

  • 2.因為我們下面的實例會用到2個長度,一個顏色值的屬性切蟋,所以我們這里先創(chuàng)建3個屬性统捶。

    <declare-styleable name="rainbowbar">
      <attr name="rainbowbar_hspace" format="dimension"></attr>
      <attr name="rainbowbar_vspace" format="dimension"></attr>
      <attr name="rainbowbar_color" format="color"></attr>
    </declare-styleable>
    

那么到底怎么用呢,我們會看一個實例。

實現(xiàn)一個比較簡單的Google彩虹進度條喘鸟。

為了簡單起見匆绣,這里我只用一種顏色,多種顏色就留給大家了什黑,我們直接上代碼崎淳。


藍色的進度條
public class RainbowBar extends View {

  //progress bar color
  int barColor = Color.parseColor("#1E88E5");
  //every bar segment width
  int hSpace = Utils.dpToPx(80, getResources());
  //every bar segment height
  int vSpace = Utils.dpToPx(4, getResources());
  //space among bars
  int space = Utils.dpToPx(10, getResources());
  float startX = 0;
  float delta = 10f;
  Paint mPaint;

  public RainbowBar(Context context) {
    super(context);
  }

  public RainbowBar(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public RainbowBar(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //read custom attrs
    TypedArray t = context.obtainStyledAttributes(attrs,
            R.styleable.rainbowbar, 0, 0);
    hSpace = t.getDimensionPixelSize(R.styleable.rainbowbar_rainbowbar_hspace, hSpace);
    vSpace = t.getDimensionPixelOffset(R.styleable.rainbowbar_rainbowbar_vspace, vSpace);
    barColor = t.getColor(R.styleable.rainbowbar_rainbowbar_color, barColor);
    t.recycle();   // we should always recycle after used
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setColor(barColor);
    mPaint.setStrokeWidth(vSpace);
  }

  .......
}

View有了三個構(gòu)造方法需要我們重寫,這里介紹下三個方法會被調(diào)用的場景愕把,

  • 第一個方法拣凹,一般我們這樣使用時會被調(diào)用,View view = new View(context);
  • 第二個方法恨豁,當我們在xml布局文件中使用View時嚣镜,會在inflate布局時被調(diào)用,
    <View
    layout_width="match_parent"
    layout_height="match_parent"/>橘蜜。
  • 第三個方法祈惶,跟第二種類似,但是增加style屬性設置扮匠,這時inflater布局時會調(diào)用第三個構(gòu)造方法捧请。
    <View
    style="@styles/MyCustomStyle"
    layout_width="match_parent"
    layout_height="match_parent"/>。

上面大家可能會感覺到有點困惑的是棒搜,我把初始化讀取自定義屬性hspace疹蛉,vspace,和barcolor的代碼寫在第三個構(gòu)造方法里面力麸,但是我RainbowBar在線性布局中沒有加style屬性()可款,那按照我們上面的解釋,inflate布局時應該會invoke第二個構(gòu)造方法啊克蚂,但是我們在第二個構(gòu)造方法里面調(diào)用了第三個構(gòu)造方法闺鲸,this(context, attrs, 0); 所以在第三個構(gòu)造方法中讀取自定義屬性,沒有問題埃叭,這是一點小細節(jié)摸恍,避免代碼冗余-,-

Draw

因為我們這里不用關(guān)注measrue和layout過程赤屋,直接重寫onDraw方法即可立镶。

 //draw be invoke numbers.
int index = 0;
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //get screen width
    float sw = this.getMeasuredWidth();
    if (startX >= sw + (hSpace + space) - (sw % (hSpace + space))) {
        startX = 0;
    } else {
        startX += delta;
    }
    float start = startX;
    // draw latter parse
    while (start < sw) {
        canvas.drawLine(start, 5, start + hSpace, 5, mPaint);
        start += (hSpace + space);
    }

    start = startX - space - hSpace;

    // draw front parse
    while (start >= -hSpace) {
        canvas.drawLine(start, 5, start + hSpace, 5, mPaint);
        start -= (hSpace + space);
    }
    if (index >= 700000) {
        index = 0;
    }
    invalidate();
}

//布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout     xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_marginTop="40dp"
android:orientation="vertical" >

<com.sw.demo.widget.RainbowBar 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:rainbowbar_color="@android:color/holo_blue_bright"
    app:rainbowbar_hspace="80dp"
    app:rainbowbar_vspace="10dp"
    ></com.sw.demo.widget.RainbowBar>

</LinearLayout>

其實就是調(diào)用canvas的drawLine方法,然后每次將draw的起點向前推進类早,在方法的結(jié)尾媚媒,我們調(diào)用了invalidate方法,上面我們已經(jīng)說明了涩僻,這個方法會讓View重新調(diào)用onDraw方法缭召,所以就達到我們的進度條一直在向前繪制的效果栈顷。下面是最后的顯示效果,制作成gif時好像有色差嵌巷,但是真實效果是藍色的妨蛹。我們只寫了短短的幾十行代碼,自定義View并不是我們想象中那么難晴竞,下一篇我們會繼續(xù)ViewGroup的繪制流程學習。


rainbow_bar_demo.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狠半,一起剝皮案震驚了整個濱河市噩死,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌神年,老刑警劉巖已维,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異已日,居然都是意外死亡垛耳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門飘千,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堂鲜,“玉大人,你說我怎么就攤上這事护奈〉蘖” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵霉旗,是天一觀的道長痴奏。 經(jīng)常有香客問我,道長厌秒,這世上最難降的妖魔是什么读拆? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮鸵闪,結(jié)果婚禮上檐晕,老公的妹妹穿的比我還像新娘。我一直安慰自己蚌讼,他們只是感情好棉姐,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啦逆,像睡著了一般伞矩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夏志,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天乃坤,我揣著相機與錄音苛让,去河邊找鬼。 笑死湿诊,一個胖子當著我的面吹牛狱杰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播厅须,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼仿畸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了朗和?” 一聲冷哼從身側(cè)響起错沽,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎眶拉,沒想到半個月后千埃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡忆植,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年放可,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朝刊。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡耀里,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拾氓,到底是詐尸還是另有隱情备韧,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布痪枫,位于F島的核電站织堂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奶陈。R本人自食惡果不足惜易阳,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吃粒。 院中可真熱鬧潦俺,春花似錦、人聲如沸徐勃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僻肖。三九已至肖爵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間臀脏,已是汗流浹背劝堪。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工冀自, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秒啦。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓熬粗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親余境。 傳聞我的和親對象是個殘疾皇子驻呐,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容