Android回顧--(二十九)View的事件傳輸與分發(fā)機(jī)制

自定義View

??在平時(shí)工作中,總會(huì)需要一些特別的需求,而這些需求是Android系統(tǒng)自帶控件不能實(shí)現(xiàn)的,所以我們就需要自定義View來實(shí)現(xiàn)業(yè)務(wù)需求。

  1. 繼承控件:主要完成的功能是在原來的控件基礎(chǔ)上添加一些新的功能
  2. 組合控件:將多個(gè)的單一的控件組合成一個(gè)控件。
  3. 完全自定義控件:編寫一個(gè)類直接繼承View,重寫里面的onMeasure()方法確定尺寸,重寫onLayout()方法確定布局位置,重寫onDraw()方法繪畫控件的形狀,再加上相應(yīng)的回調(diào)函數(shù)和自定義屬性來完成相應(yīng)的控件的展示和數(shù)據(jù)的回調(diào)。

所有的自定義View都是重寫的有兩個(gè)參數(shù)的構(gòu)造函數(shù),因?yàn)橄到y(tǒng)默認(rèn)就是調(diào)用的有兩個(gè)參數(shù)的構(gòu)造函數(shù)。

完全自定義View(控件)

  1. 自定義一個(gè)類繼承于View,并重寫里面包含兩個(gè)參數(shù)的構(gòu)造函數(shù)
  2. 重寫onMeasure()方法,用于測量自身的尺寸

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec)
widthMeasureSpec:
heightMeasureSpec:這兩個(gè)參數(shù)是從父容器得來的,這兩個(gè)參數(shù)分別實(shí)際上是兩個(gè)32位的2進(jìn)制數(shù)來表示的,這兩個(gè)32位的2進(jìn)制數(shù)的高兩位是當(dāng)前控件在xml中聲明的測量模式,后30位是父容器根據(jù)當(dāng)前的測量模式計(jì)算出來的建議的寬和高。(這個(gè)寬和高只是建議的,不是實(shí)際的)

    /**
     * 測量自身的寬高尺寸
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //下面設(shè)置默認(rèn)的寬和高
        int describWidth = 200;
        int describHeight = 200;
        //獲取寬高的測量模式
        int widthModel = MeasureSpec.getMode(widthMeasureSpec);
        int heightModel = MeasureSpec.getMode(heightMeasureSpec);
        //獲取父類建議的寬高的值
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //通過測量模式確定寬高的最終值
        int width = 0;
        int height = 0;
        if (widthModel == MeasureSpec.AT_MOST) { //說明使用的是那個(gè)warp_content模式
            width = Math.min(describWidth,widthSize);
        }else if (widthModel == MeasureSpec.EXACTLY){ //說明使用的是具體的數(shù)值或者match_parent這種測量模式
            width = widthSize;
        }
        if (heightModel == MeasureSpec.AT_MOST){
            height = Math.min(describHeight,heightSize);
        }else if (heightModel == MeasureSpec.EXACTLY){
            height = heightSize;
        }
        setMeasuredDimension(width,height);
    }

測量模式:

  1. MeasureSpec.AT_MOST:--->對(duì)應(yīng)咱們在xml布局文件中聲明的wrap_content
  2. MeasureSpec.EXACTLY:--->對(duì)應(yīng)布局文件中的match_parent/實(shí)際的數(shù)值
    默認(rèn)情況下,1和2是相等的,也就是自定義View默認(rèn)情況下是屏幕大小
  3. MeasureSpec.UNSPECIFIED:--->指的是控件的寬高不受父控件的影響,想有多高就有多高,一般用于系統(tǒng)組件,咱們用不著
  1. 重寫onDraw()方法繪制控件的形狀
    protected void onDraw(Canvas canvas) //用來畫控件的長相的
    mPaint.setAntiAlias(true); //設(shè)置抗鋸齒,為了讓邊緣平滑
    /**
     * 繪制控件的形狀
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //獲取畫筆的對(duì)象
        Paint paint = new Paint();
        paint.setColor(getResources().getColor(R.color.colorAccent));
        paint.setStrokeWidth(1);//設(shè)置線條(畫筆)的高度
        paint.setAntiAlias(true);  //設(shè)置抗鋸齒,邊緣平滑
        //獲取控件測量出來的寬高
        //這個(gè)寬高指的是控件沒有超出屏幕情況下的寬和高 如果超出了屏幕,那么這個(gè)值就是屏幕的寬高
        //getWidth:獲取的是控件的寬度
        //getHeight:獲取的是控件的高度
        //只有超出了屏幕的情況下,下面這個(gè)才能獲取真正的寬和高。如果沒有超出屏幕,那么兩種方法獲取到的值是相等的。
        getMeasuredHeight();
        getMeasuredWidth();
        //畫出自定義View的整體輪廓和形狀
        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);

    }
  1. 如果需要添加事件的話就需要回調(diào)

自定義屬性

  1. 在Values目錄下面建立一個(gè)attr的xml文件
  2. 在里面添加屬性
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="diyTextView">
        <attr name="diyText" format="string" />
        <attr name="diyColor" format="string" />
    </declare-styleable>
    <declare-styleable name="diyEditView">
        <attr name="diyEditColor" format="color" />
        <attr name="diyPadding" format="integer" />
    </declare-styleable>
</resources>
  1. 在需要使用自定義屬性的地方引入

xmlns:diyTextView ="http://schemas.android.com/apk/res-auto"

引入的時(shí)候,名字需要統(tǒng)一

diyTextView:diyText = "自定義屬性"

  1. 在自定義View中獲取屬性
  • 第一種方式:

int count = attrs.getAttributeCount();
for (int i=0;i<count;i++){
String attrName=attrs.getAttributeName(i);
String attrValue=attrs.getAttributeValue(i);
Log.e("---名稱:"+attrName,"---值:"+attrValue);
}

  • 第二個(gè)方式:

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.bobo);
String text=array.getString(R.styleable.bobo_myText);
String color=array.getString(R.styleable.bobo_myColor);
/* Log.e("------------",text);
Log.e("------------",color);*/
array.recycle(); //表示循環(huán)的取出消息
盡量使用第二種方式,因?yàn)榈谝环N方式在使用了資源引用的時(shí)候,獲取的值是資源R文件里面的引用而不是值。

這兩種獲取屬性方式的卻別是什么?

第一種方式獲取屬性值的時(shí)候,如果存在資源的引用的話沒有辦法直接獲取屬性值。第二種方式相當(dāng)于第一種方式的封裝,能夠直接通過屬性的名稱來獲取屬性的值。

刷新當(dāng)前控件的方法:

invalidate();//刷新控件不能控件的寬和高
requestLayout();//刷新控件可以改變控件的寬高

繼承控件

??一般情況下也即是繼承一個(gè)已知控件然后完成相應(yīng)的多功能操作。
實(shí)例:仿照筆記本樣式

/**
 * 仿照筆記本
 */
public class MyEditText extends EditText{
    int color=Color.BLACK;    //這個(gè)呢是這個(gè)顏色
    int padding=50;    //這個(gè)呢是那個(gè)邊距
    public MyEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        setGravity(Gravity.LEFT | Gravity.TOP );  //這里的|相當(dāng)于是與的關(guān)系,將EditText設(shè)置為從左上角開始
        setPadding(60,0,60,0);//設(shè)置左上右下的內(nèi)邊距
        setBackgroundResource(R.drawable.background );
    }
    /**
     * 重新布局自己的EditText
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint mPaint=new Paint();
        mPaint.setColor(color);    //設(shè)置那個(gè)畫筆的顏色
        mPaint.setAntiAlias(true); //設(shè)置那個(gè)抗鋸齒
        mPaint.setStrokeWidth(1);  //設(shè)置那個(gè)線條的寬度
        //開始劃線
        //從哪里劃線  到哪里結(jié)束?
        int viewHeight=getHeight();
        int viewWidth=getWidth();
        //下一步應(yīng)該獲取一共應(yīng)該畫多少線
        int lineCount=viewHeight/getLineHeight(); //計(jì)算出了一共需要畫出多少線
        for (int i=0;i<lineCount-1;i++){
            canvas.drawLine(60,(i+1)*getLineHeight(),viewWidth-60,(i+1)*getLineHeight(),mPaint);//參數(shù)分別是:起點(diǎn)x,起點(diǎn)y,終點(diǎn)x,終點(diǎn)y,畫筆
      }
}

在需要使用該控件的地方聲明

<com.qf.mobilephone.android1606_28.view2.MyEditText
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:hint="請輸入內(nèi)容"
    />

組合控件

??將多個(gè)已知的控件組合形成一個(gè)新的控件
案例:將ListView的一個(gè)item組合成一個(gè)

  1. 編寫一個(gè)View繼承于一個(gè)布局
  2. 在初始化這個(gè)控件的方法里面獲取布局加載器
  3. 編寫需要組合的控件的模板-->用xml文件描述
  4. 通過布局加載器加載布局文件
    //首先獲取那個(gè)布局的加載器
    mLayoutInflater= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//將xml文件加載給當(dāng)前對(duì)象
    View view = mLayoutInflater.inflate(R.layout.view3_layout, this);
  1. 找id,編寫方法
  2. 在使用了該控件的地方實(shí)現(xiàn)該控件中的回調(diào)接口,并重寫里面點(diǎn)擊事件。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容