自定義滑動(dòng)按鈕為例圖文剖析Android自定義View繪制
自定義View一直是橫在Android開(kāi)發(fā)者面前的一道坎。
一、View和ViewGroup的關(guān)系
從View和ViewGroup的關(guān)系來(lái)看,ViewGroup繼承View。
View的子類,多是功能型的控件,提供繪制的樣式,比如imageView,TextView等,而ViewGroup的子類,多用于管理控件的大小,位置,如LinearLayout,RelativeLayout等,從下圖可以看出
從實(shí)際應(yīng)用中看,他們又是組合關(guān)系,我們?cè)诓季种?,常常是一個(gè)ViewGroup嵌套多個(gè)ViewGroup或View,而被嵌套的ViewGroup又會(huì)嵌套多個(gè)ViewGroup或View
如下
二、View的繪制流程
從View源碼來(lái)看,主要關(guān)系三個(gè)方法:
1、measure():測(cè)量
一個(gè)final方法,控制控件的大小
2、layout():布局
用來(lái)控制自己的布局位置
有相對(duì)性,只相對(duì)于自己的父類布局,不關(guān)心祖宗布局
3、draw():繪制
用來(lái)控制控件的顯示樣式
流程: 流程 measure --> layout --> draw
對(duì)應(yīng)于我們要實(shí)現(xiàn)的方法是
onMeasure()
onLayout()
onDraw()
實(shí)際繪制中,我們的思考順序一般是這樣的:
是否需要控制控件的大小-->是-->onMeasure()
(1)如果這個(gè)自定義view不是ViewGroup,onMeasure()方法調(diào)用setMeasureDeminsion(width,height):用來(lái)設(shè)置自己的大小
(2)如果是ViewGroup,onMeasure()方法調(diào)用 ,child.measure()測(cè)量孩子的大小,給出孩子的期望大小值,之后-->setMeasureDeminsion(width,height):用來(lái)設(shè)置自己的大小
是否需要控制控件的擺放位置-->是 -->onLayout ()
是否需要控制控件的樣子-->是 -->onDraw ()-->canvas的繪制
下面是我繪制的流程圖:
下面以自定義滑動(dòng)按鈕為例,說(shuō)明自定義View的繪制流程
我們期待實(shí)現(xiàn)這樣的效果:
拖動(dòng)或點(diǎn)擊按鈕,開(kāi)關(guān)向右滑動(dòng),變成
其中開(kāi)關(guān)能隨著手指的觸摸滑動(dòng)到相應(yīng)位置,直到最后才固定在開(kāi)關(guān)位置上
新建一個(gè)類繼承自View,實(shí)現(xiàn)其兩個(gè)構(gòu)造方法
public class SwitchButtonView extends View { public SwitchButtonView(Context context) { this(context, null); } public SwitchButtonView(Context context, AttributeSet attrs) { super(context, attrs); }
drawable資源中添加這兩張圖片
借此,我們可以用onMeasure()確定這個(gè)控件的大小
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background); setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight()); }
這個(gè)控件并不需要控制其擺放位置,略過(guò)onLayout();
接下來(lái)onDraw()確定其形狀。
但我們需要根據(jù)我們點(diǎn)擊控件的不同行為來(lái)確定形狀,這需要重寫onTouchEvent()
其中的邏輯是:
當(dāng)按下時(shí),觸發(fā)事件MotionEvent.Action_Down,若此時(shí)狀態(tài)為關(guān):
(1)若手指觸摸點(diǎn)(通過(guò)event.getX()得到)在按鈕的 中線右側(cè),按鈕向右滑動(dòng)一段距離(event.getX()與開(kāi)關(guān)控件一半寬度之差,具體看下文源碼)
(2)若手指觸摸點(diǎn)在按鈕中線左側(cè),按鈕依舊處于最左(即“關(guān)”的狀態(tài))。
若此時(shí)狀態(tài)為開(kāi):
(1)若手指觸摸點(diǎn)在按鈕中線左側(cè),按鈕向左滑動(dòng)一段距離
(2)若手指觸摸點(diǎn)在按鈕中線右側(cè),按鈕依舊處于最右(即“開(kāi)”的狀態(tài))
當(dāng)滑動(dòng)時(shí),觸發(fā)時(shí)間MotionEvent.Action_MOVE,邏輯與按下時(shí)一致
注意,onTouchEvent()需要設(shè)置返回值 為 Return true,否則無(wú)法響應(yīng)滑動(dòng)事件
當(dāng)手指收起時(shí),若開(kāi)關(guān)中線位于整個(gè)控件中線左側(cè),設(shè)置狀態(tài)為關(guān),反之,設(shè)置為開(kāi)。
具體源碼如下所示:(還對(duì)外提供了一個(gè)暴露此時(shí)開(kāi)關(guān)狀態(tài)的接口)
自定義View部分
package com.lian.switchtogglebutton; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * Created by lian on 2016/3/20. */ public class SwitchButtonView extends View { private static final int STATE_NULL = 0;//默認(rèn)狀態(tài) private static final int STATE_DOWN = 1; private static final int STATE_MOVE = 2; private static final int STATE_UP = 3; private Bitmap mSlideButton; private Bitmap mSwitchButton; private Paint mPaint = new Paint(); private int buttonState = STATE_NULL; private float mDistance; private boolean isOpened = false; private onSwitchListener mListener; public SwitchButtonView(Context context) { this(context, null); } public SwitchButtonView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background); setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mSwitchButton!= null){ canvas.drawBitmap(mSwitchButton, 0, 0, mPaint); } //buttonState的值在onTouchEvent()中確定 switch (buttonState){ case STATE_DOWN: case STATE_MOVE: if (!isOpened){ float middle = mSlideButton.getWidth() / 2f; if (mDistance > middle) { float max = mSwitchButton.getWidth() - mSlideButton.getWidth(); float left = mDistance - middle; if (left >= max) { left = max; } canvas.drawBitmap(mSlideButton,left,0,mPaint); } else { canvas.drawBitmap(mSlideButton,0,0,mPaint); } }else{ float middle = mSwitchButton.getWidth() - mSlideButton.getWidth() / 2f; if (mDistance < middle){ float left = mDistance-mSlideButton.getWidth()/2f; float min = 0; if (left < 0){ left = min; } canvas.drawBitmap(mSlideButton,left,0,mPaint); }else{ canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint); } } break; case STATE_NULL: case STATE_UP: if (isOpened){ Log.d("開(kāi)關(guān)","開(kāi)著的"); canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint); }else{ Log.d("開(kāi)關(guān)","關(guān)著的"); canvas.drawBitmap(mSlideButton,0,0,mPaint); } break; default: break; } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mDistance = event.getX(); Log.d("DOWN","按下"); buttonState = STATE_DOWN; invalidate(); break; case MotionEvent.ACTION_MOVE: buttonState = STATE_MOVE; mDistance = event.getX(); Log.d("MOVE","移動(dòng)"); invalidate(); break; case MotionEvent.ACTION_UP: mDistance = event.getX(); buttonState = STATE_UP; Log.d("UP","起開(kāi)"); if (mDistance >= mSwitchButton.getWidth() / 2f){ isOpened = true; }else { isOpened = false; } if (mListener != null){ mListener.onSwitchChanged(isOpened); } invalidate(); break; default: break; } return true; } public void setOnSwitchListener(onSwitchListener listener){ this.mListener = listener; } public interface onSwitchListener{ void onSwitchChanged(boolean isOpened); } }
DemoActivity:
package com.lian.switchtogglebutton; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SwitchButtonView switchButtonView = (SwitchButtonView) findViewById(R.id.switchbutton); switchButtonView.setOnSwitchListener(new SwitchButtonView.onSwitchListener() { @Override public void onSwitchChanged(boolean isOpened) { if (isOpened) { Toast.makeText(MainActivity.this, "打開(kāi)", Toast.LENGTH_SHORT).show(); }else { Toast.makeText(MainActivity.this, "關(guān)閉", Toast.LENGTH_SHORT).show(); } } }); } }
布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.lian.switchtogglebutton.MainActivity"> <com.lian.switchtogglebutton.SwitchButtonView android:id="@+id/switchbutton" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Android實(shí)現(xiàn)自動(dòng)匹配關(guān)鍵字并且標(biāo)紅功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)自動(dòng)匹配關(guān)鍵字并且標(biāo)紅功能,單關(guān)鍵字和多關(guān)鍵字進(jìn)行匹配,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05AndroidStudio Gradle基于友盟的多渠道打包方法
這篇文章主要介紹了AndroidStudio Gradle基于友盟的多渠道打包方法,需要的朋友可以參考下2017-09-09Android SeekBar 自定義thumb旋轉(zhuǎn)動(dòng)畫(huà)效果
某些音樂(lè)播放或者視頻播放的界面上,資源還在加載時(shí),進(jìn)度條的原點(diǎn)(thumb)會(huì)顯示一個(gè)轉(zhuǎn)圈的效果。這篇文章主要介紹了Android SeekBar 自定義thumb thumb旋轉(zhuǎn)動(dòng)畫(huà)效果,需要的朋友可以參考下2021-11-11Android 中WallpaperManager用法實(shí)例
這篇文章主要介紹了Android 中WallpaperManager用法實(shí)例的相關(guān)資料,希望通過(guò)本文能幫助到大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-09-09新浪微博第三方登錄界面上下拉伸圖片之第三方開(kāi)源PullToZoomListViewEx(一)
PullZoomView要實(shí)現(xiàn)兩類,一類是典型的Android ListView,另外一類是Android 的scroll view。本文先介紹PullZoomView在ListView上的實(shí)現(xiàn):PullToZoomListViewEx2015-12-12Android開(kāi)發(fā)之TextView控件用法實(shí)例總結(jié)
這篇文章主要介紹了Android開(kāi)發(fā)之TextView控件用法,結(jié)合實(shí)例形式總結(jié)分析了TextView控件常用的屬性設(shè)置及使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-02-02