Android自定義View

  1. View的分類
    視圖View主要分為兩類:

單一視圖
即一個(gè)View稻薇,不含子View
視圖組
即多個(gè)View組成的View淑际,比如LinerLayout奔穿,包含子View

  1. View類的簡介
  • View類是Android中所有組件的基類闻鉴,如View是ViewGroup的基類
  • View的構(gòu)造函數(shù)

自定義View必須重寫至少一個(gè)構(gòu)造函數(shù)

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

// 如果View是在.xml里聲明的,則調(diào)用第二個(gè)構(gòu)造函數(shù)
// 自定義屬性是從AttributeSet參數(shù)傳進(jìn)來的
    public  XiaoCaiView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

// 不會(huì)自動(dòng)調(diào)用
// 一般是在第二個(gè)構(gòu)造函數(shù)里主動(dòng)調(diào)用
// 如View有style屬性時(shí)
    public  XiaoCaiView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //API21之后才使用
    // 不會(huì)自動(dòng)調(diào)用
    // 一般是在第二個(gè)構(gòu)造函數(shù)里主動(dòng)調(diào)用
    // 如View有style屬性時(shí)
    public  XiaoCaiView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
  1. 測量View大小(onMeasure)
  • 自定義View的測量方法
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measureWidth = measureWidth(widthMeasureSpec);
        int measureHeight = measureHeight(heightMeasureSpec);
        setMeasuredDimension(measureWidth, measureHeight);
        //布局的寬高都是由這個(gè)方法指定
        //指定控件的寬高芒篷,需要測量
        //獲取寬高的模式
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        //控件的寬
        int width=MeasureSpec.getSize(widthMeasureSpec);
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);
        //控件的高
        int height=MeasureSpec.getSize(heightMeasureSpec);
        //
        if(widthMode==MeasureSpec.AT_MOST){
         //在布局中定義warp_content
        }
        if(widthMode==MeasureSpec.EXACTLY){
         //在布局中制定了具體的值:100dp
        }
        if(widthMode==MeasureSpec.UNSPECIFIED){
            //盡可能的大
        }
    }
    private int measureHeight(int heightMeasureSpec) {
        int defaultHeight=0;
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        switch (heightMode) {
            case MeasureSpec.UNSPECIFIED:
                defaultHeight = height;
                break;
            case MeasureSpec.AT_MOST:
                defaultHeight = height / 4;
                break;
            case MeasureSpec.EXACTLY:
                defaultHeight = height / 5;
                break;
            default:
                break;
        }
        return defaultHeight;
    }
模式 二進(jìn)制數(shù)值 描述
UNSPECIFIED 00 默認(rèn)值搜变,父控件沒有給子View任何限制,子View可以設(shè)置為任意大小
EXACTLY 01 父控件給子View限制了具體的數(shù)值
AT MOST 10 表示子View具體大小沒有尺寸限制针炉,但是存在上限挠他,上限一般為父View大小。
  • setMeasuredDimension
    onMeasure方法最后需要調(diào)用setMeasuredDimension方法來保存測量的寬高值篡帕,如果不調(diào)用這個(gè)方法绩社,可能會(huì)產(chǎn)生不可預(yù)測的問題摔蓝。
  1. 確定View大小(onSizeChanged)
    這個(gè)函數(shù)在視圖發(fā)生改變的時(shí)候調(diào)用
    因?yàn)閂iew的大小不僅由View本身控制,而且受父控件的影響愉耙,所以我們在確定View大小的時(shí)候最好使用系統(tǒng)提供的onSizeChanged回調(diào)函數(shù)贮尉。
    onSizeChanged()函數(shù)如下:
 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

可以看出,它又四個(gè)參數(shù)朴沿,分別為 寬度猜谚,高度,上一次寬度赌渣,上一次高度魏铅。
這個(gè)函數(shù)比較簡單,我們只需關(guān)注 寬度(w), 高度(h) 即可坚芜,這兩個(gè)參數(shù)就是View最終的大小览芳。

  1. 確定子View布局位置(onLayout)
  • 自定義一個(gè)可以滑動(dòng)的ViewGroup來體會(huì)一下OnLayout()吧
    1.onMeasure()方法告訴子View去測量自己的大小
     /**
     * 
     * 使用遍歷的方式通知子view進(jìn)行自測
     * 
     * */
     @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount=getChildCount();
        for(int i=0;i<childCount;i++){
            View child=getChildAt(i);
            measureChild(child,widthMeasureSpec,heightMeasureSpec);
        }
    }
    
    1. onLayout():放置每個(gè)子View的位置
     @Override
      protected void onLayout(boolean b, int left, int up, int right, int down) {
         // LinearLayout root=new LinearLayout(getContext());
         // View child=new View(getContext());
         // LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams
          //      .WRAP_CONTENT);
        //  lp.setMargins(10,10,10,10);
        //  root.addView(child,lp);
          int childCout = getChildCount();
          MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
          mlp.height = childCout * mScreenHeight;
          //設(shè)置ViewGroup的高
          setLayoutParams(mlp);
          for (int i = 0; i < childCout; i++) {
              View child = getChildAt(i);
              if (child.getVisibility() != View.GONE) {
                  //設(shè)置每個(gè)child在ViewGroup中的位置
                  child.layout(left, i * mScreenHeight, right, (i + 1) * mScreenHeight);
              }
          }
      }
    
    1. OnTouchEvent()實(shí)現(xiàn)滑動(dòng)邏輯:
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         int y = (int) event.getY();
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 // 記錄觸摸起點(diǎn)
                 mStart = getScrollY();
                 mLastY = y;
                 break;
             case MotionEvent.ACTION_MOVE:
                 if (!mScroller.isFinished()) {
                     mScroller.abortAnimation();
                 }
                 //滑動(dòng)的距離
                 int dy = mLastY - y;
                 if (getScrollY() < 0 || getScrollY() > getHeight() - mScreenHeight) {
                     dy = 0;
                 }
                 scrollBy(0, dy);
                 mLastY = y;
                 break;
             case MotionEvent.ACTION_UP:
                 // 記錄觸摸終點(diǎn)
                 mEnd = getScrollY();
                 int dScrollY = mEnd - mStart;
                 if (dScrollY > 0) {
                     if (dScrollY < mScreenHeight / 3) {
                         mScroller.startScroll(
                                 0, getScrollY(),
                                 0, -dScrollY);
                     } else {
                         mScroller.startScroll(
                                 0, getScrollY(),
                                 0, mScreenHeight - dScrollY);
                     }
                 } else {
                     if (-dScrollY < mScreenHeight / 3) {
                         mScroller.startScroll(
                                 0, getScrollY(),
                                 0, -dScrollY);
                     } else {
                         mScroller.startScroll(
                                 0, getScrollY(),
                                 0, -mScreenHeight - dScrollY);
                     }
                 }
                 break;
             default:
                 break;
         }
         postInvalidate();
         return true;
     }
    

6. onDraw()(繪制內(nèi)容)
這個(gè)是自定義控件最核心的一個(gè)類,需要畫的東東都在這個(gè)里面鸿竖。平時(shí)我們畫什么東西的時(shí)候是不是需要一個(gè)本子沧竟,一個(gè)畫筆。那么這個(gè)onDraw里面也需要畫布(Canvas)和畫筆(Paint)接下來就介紹一下Canvas和Paint:

  • Canvas簡介
    Canvas我們可以稱之為畫布缚忧,能夠在上面繪制各種東西悟泵,是安卓平臺2D圖形繪制的基礎(chǔ),非常強(qiáng)大闪水。
  • Canvas的常用操作速查表
操作類型 相關(guān)API 備注
繪制顏色 drawColor, drawRGB, drawARGB 使用單一顏色填充整個(gè)畫布
繪制基本形狀 drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc 依次為 點(diǎn)糕非、線、矩形球榆、圓角矩形朽肥、橢圓、圓持钉、圓弧
繪制圖片 drawBitmap, drawPicture 繪制位圖和圖片
繪制文本 drawText, drawPosText, drawTextOnPath 依次為 繪制文字衡招、繪制文字時(shí)指定每個(gè)文字位置、根據(jù)路徑繪制文字
繪制路徑 drawPath 繪制路徑右钾,繪制貝塞爾曲線時(shí)也需要用到該函數(shù)
頂點(diǎn)操作 drawVertices, drawBitmapMesh 通過對頂點(diǎn)操作可以使圖像形變蚁吝,drawVertices直接對畫布作用旱爆、 drawBitmapMesh只對繪制的Bitmap作用
畫布剪裁 clipPath, clipRect 設(shè)置畫布的顯示區(qū)域
畫布快照 save, restore, saveLayerXxx, restoreToCount, getSaveCount 依次為 保存當(dāng)前狀態(tài)舀射、 回滾到上一次保存的狀態(tài)、 保存圖層狀態(tài)怀伦、 回滾到指定狀態(tài)脆烟、 獲取保存次數(shù)
畫布變換 translate, scale, rotate, skew 依次為 位移、縮放房待、 旋轉(zhuǎn)邢羔、錯(cuò)切
Matrix(矩陣) getMatrix, setMatrix, concat 實(shí)際畫布的位移驼抹,縮放等操作的都是圖像矩陣Matrix,只不過Matrix比較難以理解和使用拜鹤,故封裝了一些常用的方法框冀。
  • 簡要介紹Paint
    繪制的基本形狀由Canvas確定,但繪制出來的顏色,具體效果則由Paint確定敏簿。
    畫筆有三種模式明也,如下:
STROKE                //描邊
FILL                  //填充
FILL_AND_STROKE       //描邊加填充

為了區(qū)分三者效果我們做如下實(shí)驗(yàn):

        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(40);     //為了實(shí)驗(yàn)效果明顯,特地設(shè)置描邊寬度非常大

        // 描邊
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(200,200,100,paint);

        // 填充
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(200,500,100,paint);

        // 描邊加填充
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawCircle(200, 800, 100, paint);

7.Path簡介
很多時(shí)候我們的一些API方法畫出的達(dá)不到我們想要的一些酷炫效果惯裕,這時(shí)候就需要用到Path路徑了温数,Path封裝了由直線和曲線(二次,三次貝塞爾曲線)構(gòu)成的幾何路徑蜻势。你能用Canvas中的drawPath來把這條路徑畫出來(同樣支持Paint的不同繪制模式)撑刺,也可以用于剪裁畫布和根據(jù)路徑繪制文字。我們有時(shí)會(huì)用Path來描述一個(gè)圖像的輪廓握玛,所以也會(huì)稱為輪廓線(輪廓線僅是Path的一種使用方法够傍,兩者并不等價(jià))。

  • 最常用的方法:moveTo败许、 setLastPoint王带、 lineTo 和 close
    moveTo:將點(diǎn)移動(dòng)到相應(yīng)的位置
    setLastPoint:設(shè)置之前操作的最后一個(gè)點(diǎn)位置
    lineTo:連接上一個(gè)點(diǎn)和該點(diǎn)構(gòu)成線
    close:形成閉合曲線

8.貝塞爾曲線簡介

  • 貝塞爾曲線主要是找到三個(gè)點(diǎn),起始點(diǎn)市殷,控制點(diǎn)愕撰,終止點(diǎn)。其中醋寝,起始點(diǎn)和終止點(diǎn)稱為數(shù)據(jù)點(diǎn)搞挣。
類型 作用
數(shù)據(jù)點(diǎn) 確定曲線的起始和結(jié)束位置
控制點(diǎn) 確定曲線的彎曲程度
  • 貝塞爾曲線的應(yīng)用:
  • QQ小紅點(diǎn)拖拽效果
  • 一些炫酷的下拉刷新控件
  • 閱讀軟件的翻書效果
  • 一些平滑的折線圖的制作
  • 很多炫酷的動(dòng)畫效果

實(shí)例講解
簡單的QQ拖拽效果

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市音羞,隨后出現(xiàn)的幾起案子囱桨,更是在濱河造成了極大的恐慌,老刑警劉巖嗅绰,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舍肠,死亡現(xiàn)場離奇詭異,居然都是意外死亡窘面,警方通過查閱死者的電腦和手機(jī)翠语,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來财边,“玉大人肌括,你說我怎么就攤上這事『眩” “怎么了谍夭?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵黑滴,是天一觀的道長。 經(jīng)常有香客問我紧索,道長袁辈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任珠漂,我火速辦了婚禮吵瞻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘甘磨。我一直安慰自己橡羞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布济舆。 她就那樣靜靜地躺著卿泽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滋觉。 梳的紋絲不亂的頭發(fā)上签夭,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機(jī)與錄音椎侠,去河邊找鬼第租。 笑死,一個(gè)胖子當(dāng)著我的面吹牛我纪,可吹牛的內(nèi)容都是我干的慎宾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼浅悉,長吁一口氣:“原來是場噩夢啊……” “哼趟据!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起术健,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤汹碱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后荞估,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咳促,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年勘伺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了跪腹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娇昙,死狀恐怖尺迂,靈堂內(nèi)的尸體忽然破棺而出笤妙,到底是詐尸還是另有隱情冒掌,我是刑警寧澤噪裕,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站股毫,受9級特大地震影響膳音,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铃诬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一祭陷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趣席,春花似錦兵志、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霉涨,卻和暖如春按价,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笙瑟。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工楼镐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人往枷。 一個(gè)月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓框产,卻偏偏與公主長得像,于是被迫代替她去往敵國和親错洁。 傳聞我的和親對象是個(gè)殘疾皇子茅信,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

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