您好,登錄后才能下訂單哦!
如何在Android中自定義View?很多新手對此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
一、拓展谷歌提供的系統(tǒng)控件
假如我們要對Textview控件進(jìn)行拓展,首先我們要定義一個(gè)類繼承TextView,選擇性的重寫它的onDraw()、onMeasure()、onTouchEvent()等方法。其中,onDraw()負(fù)責(zé)對圖像的繪制,onMeasure()負(fù)責(zé)測量位置,onTouchEvent()負(fù)責(zé)設(shè)置觸摸的事件。當(dāng)我們想直接繪制出有背景顏色的TextView時(shí),可以在類中定義畫筆,在onDraw()進(jìn)行繪制。代碼如下:
Paint paint1=new Paint(); //定義畫筆 paint1.setColor(Color.YELLOW); paint1.setStyle(Paint.Style.FILL);
然后,通過以下的代碼,就可以繪制出一個(gè)帶矩形框的Textview,但是需要在繪制完成后在調(diào)用父類的onDraw(),因?yàn)槭窃谙到y(tǒng)控件上拓展,所以,還要有其原來的功能。
@Override protected void onDraw(Canvas canvas) { canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paint1);//繪制矩形 canvas.save(); super.onDraw(canvas); canvas.restore(); }
使用canvas對象就可以進(jìn)行繪圖了,對canvas的講解,我將會在下一篇博客講解。
然后,我們只需要在布局文件中加入自定義的控件即可,在布局文件中,自定義view的名字就是自定義控件類的包名加上類名,假設(shè)定義CustomTextview類繼承TextView,例子如下:
<com.example.myapplication.View.CustomTextView android:layout_width="wrap_content" android:layout_height="match_parent"></com.example.myapplication.View.Buttonbtn>
二、將系統(tǒng)提供的控件組合在一起
除了拓展原有的控件以外,我們還可以將控件組合成一個(gè)新的控件使用。首先,我們先定義一個(gè)新的布局文件,并把Imageview和Textview加入,代碼如下。
<ImageView android:id="@+id/iv" android:layout_width="20dp" android:layout_height="20dp" android:src="@mipmap/ic_launcher" /> <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="2dp" android:text="消息" android:textSize="13sp" />
然后我們定義一個(gè)類繼承LinearLayout,在類的構(gòu)造方法中對控件和布局進(jìn)行初始化。
public void init(Context context) { //指定線性布局的顯示方式,垂直 setOrientation(VERTICAL); //設(shè)置用戶期望的布局方式 LayoutParams mLayoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); setLayoutParams(mLayoutParams); setGravity(Gravity.CENTER); setPadding(4, 4, 4, 4); //設(shè)置其布局文件 View mButtonbtnView = LayoutInflater.from(context).inflate(layout.botton_btn_view, this, true); mImageView = mButtonbtnView.findViewById(id.iv); mTextView = mButtonbtnView.findViewById(id.tv); }
接下來,它的使用方法就和拓展控件的方法一樣了,直接在布局文件中,加入控件即可。
<com.example.myapplication.View.Buttonbtn android:layout_width="wrap_content" android:layout_height="match_parent"></com.example.myapplication.View.Buttonbtn>
三、重寫View來實(shí)現(xiàn)全新的控件
當(dāng)系統(tǒng)原生的控件無法滿足我們需求時(shí),我們就可以定義一個(gè)新的控件來完成需要的功能。創(chuàng)建一個(gè)新的控件,需要繼承View類,其難點(diǎn)主要在于繪制控件和實(shí)現(xiàn)交互。在繼承View類時(shí),我們還需要重寫它的onDraw(),onMeasure()、onTouchEvent()來實(shí)現(xiàn)繪制、測量和觸摸事件。
onDraw()繪制就是在canvas對象上調(diào)用其一系列方法進(jìn)行繪圖,繪制控件的形狀。
onMeasure()
下面,我來講講onMeasure()。在繪制View之前,我們需要告訴系統(tǒng)我們需要畫一個(gè)多大的View以及他的位置,這就是onMeasure()進(jìn)行的了。首先,我們來了解一下測量的三種模式:
EXACTLY:精確值模式,在指定view具體數(shù)值的時(shí)候會用到。
AT_MOST:最大值模式,將控件設(shè)置為"wrap_content"用到,它會根據(jù)子控件或者內(nèi)容變化而變化。
UNSPECIFIED:繪制控件想要多大就可以多大。
根據(jù)以上三種模式,我們就可以在測量的時(shí)候判斷和使用了。首先,我們重寫一個(gè)view的onMeasure()方法。再通過使用MeasureSpec類獲得控件的測量模式。MeasureSpec使用的是位運(yùn)算,其高2位為測量的模式,剩下的30位為測量的大小。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { } else if (widthMode == MeasureSpec.AT_MOST) { } else if (widthMode == MeasureSpec.UNSPECIFIED) { } }
以上代碼就是通過判斷測量模式來給定義控件的大小,這里只是測量了控件的寬度,控件高度的測量也是類似的,就不在做詳解。
前面說過,ViewGroup是用來管理控件的,當(dāng)ViewGroup的大小為"wrap_content"時(shí),它就會遍歷其所有子View,來獲得子View的大小,再來設(shè)置自身的大小。我們使用過的布局,像RelativeLayout,LinearLayout都是繼承ViewGroup的,所以他們也是使用這種方法來獲得自己的大小的。
onTouchEvent()
onTouchEvent()就是我們所說的觸摸事件,由于Android手機(jī)是觸屏的,所以我們自定義View在觸摸屏幕的時(shí)候,也需要有一定的處理來完成交互。當(dāng)重寫onTouchEvent方法的時(shí)候,我們可以看到,需要傳入MotionEvent的對象。我們可以通過這個(gè)類來設(shè)置觸摸的事件,也可以獲得觸摸點(diǎn)的位置。我們可以通過getAction()來獲取觸摸事件的行動,來判斷是否按下屏幕或者移動。在Android的坐標(biāo)系中,我們都知道Android的屏幕在豎屏的時(shí)候,以左上角的位置為原點(diǎn),向右為x軸的正方向,向下為y軸的正方向,知道了這個(gè)后,我們就可以通過調(diào)用getX()和getY()方法可以獲取觸摸點(diǎn)的坐標(biāo),來完成一些交互操作。
public boolean onTouchEvent(MotionEvent event) { float x; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { x=event.getX(); } break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; } return true; }
以上就是自定義控件常用重寫的方法,通過了重寫這幾個(gè)方法,我們基本就可以實(shí)現(xiàn)一個(gè)簡易的自定義控件了。下面,我們來了解下控件的事件攔截機(jī)制的原理。
事件攔截機(jī)制分析
我們前面講過,控件結(jié)構(gòu)是樹形結(jié)構(gòu),一個(gè)ViewGroup中可能有多個(gè)ViewGroup或者View,那么,觸摸事件是怎么準(zhǔn)確的分配給每個(gè)View和ViewGroup的呢。我們假設(shè)有一個(gè)ViewGroupA,在他的里面嵌套著ViewGroupB,而在ViewGroupB的里面,又嵌套著一個(gè)View。當(dāng)我們重寫ViewGroupA類的時(shí)候,就需要重寫里面的這三個(gè)方法:
dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()
而在重寫View的時(shí)候,需要重寫兩個(gè)方法:
dispatchTouchEvent()
onTouchEvent()
可以根據(jù)名字看出,ViewGroup中比View多了onInterceptTouchEvent()方法,這個(gè)方法就是事件攔截的核心。在每一個(gè)方法中Log一下,再點(diǎn)擊View的時(shí)候,就會發(fā)現(xiàn)方法調(diào)用的順序:
首先,調(diào)用了ViewGroupA類的dispatchTouchEvent()和onInterceptTouchEvent()。
再調(diào)用了ViewGroupB類的dispatchTouchEvent()和onInterceptTouchEvent()。
再到View的dispatchTouchEvent()方法。
這個(gè)調(diào)用的順序就是事件傳遞的順序,而事件處理的順序則是:
View的onTouchEvent()。
ViewGroupB的onTouchEvent()。
ViewGroupA的onTouchEvent()。
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。