自定義View

分類:
  • 繼承View重寫onDraw方法:這種情況下颖榜,需要自己支持wrap_content炬转,并且padding也需要自己處理笙瑟。
  • 繼承ViewGroup派生特殊的layout:需要處理ViewGroup的測量和布局這兩個過程,并同時處理子元素的測量和布局過程喷橙。
  • 繼承特定的View(比如TextView):這種方法一般不需要自己處理wrap_content和padding啥么。
  • 繼承特定的ViewGroup(比如LinearLayout)
自定義View須知:
  • 對于繼承View和ViewGroup的情況下,需要處理wrap_content和padding
  • 盡量不要在View中使用handler,因為View內(nèi)部本身就提供了post系列贰逾。
  • View中如果有線程或動畫悬荣,需要及時停止。當(dāng)View不可見或View的Activity推出時疙剑,要及時停止線程和動畫氯迂,否則有可能造成內(nèi)存泄漏践叠。我們一般是在onDetacheddFromWindow中去停止,因為當(dāng)View不可見或Activity退出時嚼蚀,該方法會被調(diào)用禁灼。
  • View帶有滑動嵌套情形時,需要處理好滑動沖突轿曙。
示例一:繼承View重寫onDraw方法

這里我們來實現(xiàn)自定義圓弄捕,首先看到onMeasure方法:
解析傳入的寬高 --- 考慮wrap_contet情況 --- 若存在,則設(shè)置一個默認的值

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取到width导帝,height對應(yīng)的mode和size
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        //考慮wrap_content的情況守谓,需要為其設(shè)置默認的值,這里設(shè)置為200dp
        //對于寬高都設(shè)定為wrap_content時
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200, 200);
        }
        //對于高設(shè)定為wrap_content的情況
        else if (widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200, heightSpecSize);
        }
        //對于寬設(shè)定為wrap_content的情況
        else if (heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

接下來是您单,實現(xiàn)onDraw方法:
要注意的一點是斋荞,這里要處理padding的情況

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //獲取到padding值
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        //獲取到真實的寬高
        int height = getHeight() - paddingTop - paddingBottom;
        int width = getWidth() - paddingLeft - paddingRight;
        //以最小的值/2作為半徑
        int radius = Math.min(width, height) / 2;
        //進行畫圓
        canvas.drawCircle(paddingLeft + width/2 , paddingTop + height/2, radius, mPaint);
    }

我們還可以為其設(shè)置自定義屬性:
a. 在res/values中創(chuàng)建一個xml文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleView">
        <attr name="circle_color" format="color"/>
    </declare-styleable>
</resources>

其中,name表示為屬性名虐秦,format表示為屬性類型平酿,這里是color類型。

b. 在布局中引入:xmlns:app = "http://schemas.android.com/apk/res-auto"
并設(shè)置自定義控件:

<com.example.viewtest.CircleView
        android:id="@+id/circleView"
        android:background="#000"
        app:circle_color = "@color/gray_color"
        android:padding="10dp"
        android:layout_margin="10dp"
        android:layout_width="200dp"
        android:layout_height="wrap_content" />

我們可以看到其中的app:cirle_color表示為自定義的屬性羡疗。

c. 在自定義View中獲取到屬性:

//獲取到自定義屬性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
//獲取到自定義屬性的值,其中第二個參數(shù)為默認參數(shù)别洪,表示若不存在叨恨,則設(shè)置為默認值
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);

完整代碼如下:

public class CircleView extends View {
    //定義顏色和畫筆
    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public CircleView(Context context) {
        super(context);
        init();
    }

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //獲取到自定義屬性
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        //獲取到自定義屬性的值,其中第二個參數(shù)為默認參數(shù)挖垛,表示若不存在痒钝,則設(shè)置為默認值
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
        init();
    }

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
        init();
    }

    //給畫筆設(shè)置顏色
    private void init(){
        mPaint.setColor(mColor);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取到width,height對應(yīng)的mode和size
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        //考慮wrap_content的情況痢毒,需要為其設(shè)置默認的值送矩,這里設(shè)置為200dp
        //對于寬高都設(shè)定為wrap_content時
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200, 200);
        }
        //對于高設(shè)定為wrap_content的情況
        else if (widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200, heightSpecSize);
        }
        //對于寬設(shè)定為wrap_content的情況
        else if (heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //獲取到padding值
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        //獲取到真實的寬高
        int height = getHeight() - paddingTop - paddingBottom;
        int width = getWidth() - paddingLeft - paddingRight;
        //以最小的值/2作為半徑
        int radius = Math.min(width, height) / 2;
        //進行畫圓
        canvas.drawCircle(paddingLeft + width/2 , paddingTop + height/2, radius, mPaint);
    }
}

實現(xiàn)效果:


自定義View.png
實例二:自定義的ViewGroup

實現(xiàn)一個類似ViewPager的自定義ViewGroup:

a. 實現(xiàn)onMeasure方法:
遍歷孩子元素進行測量 --- 考慮wrap_content情況:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //定義兩個變量,作為測量的寬和高
        int measureWidth = 0;
        int measureHeight = 0;
        //獲取到孩子個數(shù)
        final int childCount = getChildCount();
        //遍歷孩子元素哪替,對孩子元素分別進行測量
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        
        //解析寬高栋荸,獲取到模式和大小
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        //考慮wrap_content情況
        
        //孩子為0的情況下
        if (childCount == 0){
            setMeasuredDimension(0 ,0);
        }
        //寬高都是為wrap_content
        else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            //獲取到對應(yīng)的寬度
            measureWidth = childView.getMeasuredWidth() * childCount;
            //設(shè)置孩子的高度作為當(dāng)前高度
            measureHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measureWidth, measureHeight);
        } else if(widthSpecMode == MeasureSpec.AT_MOST){
            final  View childView = getChildAt(0);
            measureWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measureWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            measureHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpecSize, measureHeight);
        }
    }

b. 實現(xiàn)布局方式:
遍歷所有的孩子元素,若孩子元素不為GONE模式凭舶,則進行放置其位置晌块。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;
        final int childCount = getChildCount();
        mChildrenSize = childCount;
        //遍歷所有的孩子元素
        for (int i = 0; i < childCount; i ++){
            final View childView = getChildAt(i);
            //若當(dāng)前的孩子元素不為GONE,則對該孩子進行放置其位置
            if (childView.getVisibility() != View.GONE){
                final int childWidth = childView.getMeasuredWidth();
                mChildWidth = childWidth;
                childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帅霜,一起剝皮案震驚了整個濱河市匆背,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌身冀,老刑警劉巖钝尸,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件括享,死亡現(xiàn)場離奇詭異,居然都是意外死亡珍促,警方通過查閱死者的電腦和手機铃辖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踢星,“玉大人澳叉,你說我怎么就攤上這事°逶茫” “怎么了成洗?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長藏否。 經(jīng)常有香客問我瓶殃,道長,這世上最難降的妖魔是什么副签? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任遥椿,我火速辦了婚禮,結(jié)果婚禮上淆储,老公的妹妹穿的比我還像新娘冠场。我一直安慰自己,他們只是感情好本砰,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布碴裙。 她就那樣靜靜地躺著,像睡著了一般点额。 火紅的嫁衣襯著肌膚如雪舔株。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天还棱,我揣著相機與錄音载慈,去河邊找鬼。 笑死珍手,一個胖子當(dāng)著我的面吹牛办铡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播琳要,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼料扰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了焙蹭?” 一聲冷哼從身側(cè)響起晒杈,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孔厉,沒想到半個月后拯钻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帖努,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年粪般,在試婚紗的時候發(fā)現(xiàn)自己被綠了拼余。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡亩歹,死狀恐怖匙监,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情小作,我是刑警寧澤亭姥,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站顾稀,受9級特大地震影響达罗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜静秆,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一粮揉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抚笔,春花似錦扶认、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蛀柴,卻和暖如春螃概,著一層夾襖步出監(jiān)牢的瞬間矫夯,已是汗流浹背鸽疾。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留训貌,地道東北人制肮。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像递沪,于是被迫代替她去往敵國和親豺鼻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

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