標籤:android
源碼和測試例子已經放到github https://github.com/Leaking/SlideSwitch
如下,傳不了gif圖片,暫且截一個靜態圖片。其中按鈕滑動時顏色有漸層效果。
先說說大概思路:按鈕繪製了三個圖層,最下面是覆蓋整個View的灰色,第二個是覆蓋整個View的自訂的顏色,它可以改變透明度。第三個是白色。當白色部分移動時,修改第二個圖層的透明度即可。
大概記錄一下重寫一個組件的實現過程。
1,定義屬性
2,,在Java代碼中的構造器擷取屬性的值
3,重寫onMeasure()
4,重寫onDraw()
5,如果需要,重寫onTouch監聽
6,在自己項目的布局檔案中,定義一個命名空間,使用屬性
1,定義屬性
建立一個項目,命名為SlieSwitch,並建立屬性檔案SlideSwitch\res\values\attrs.xml
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="slideswitch"> <attr name="themeColor" format="color" /> <attr name="isOpen" format="boolean" /> <attr name="shape"> <enum name="rect" value="1" /> <enum name="circle" value="2" /> </attr> </declare-styleable></resources>
其中定義了三個屬性,一個是按鈕的顏色,一個是按鈕的開啟狀態,一個是枚舉類型,它用來描述按鈕的形狀,關於這些的屬性以及其它幾個類型的屬性的使用,可以容易百度Google到相關知識,這裡暫不作記錄。
2,,在Java代碼中的構造器擷取屬性的值
在SlieSwitch中建立一個包com.leaking.slideswitch,然後再包裡面建立一個檔案檔案SlieSwitch.java。自訂群組件首先得繼承View類,然後再構造器中擷取自訂屬性,如下艾瑪片段:
public class SlideSwitch extends View {//,,,,,,,省略,,,,,,,,,,public SlideSwitch(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);listener = null;paint = new Paint();paint.setAntiAlias(true);TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.slideswitch);color_theme = a.getColor(R.styleable.slideswitch_themeColor,COLOR_THEME);isOpen = a.getBoolean(R.styleable.slideswitch_isOpen, false);shape = a.getInt(R.styleable.slideswitch_shape, SHAPE_RECT);a.recycle();}public SlideSwitch(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SlideSwitch(Context context) {this(context, null);} //,,,,,,,省略,,,,,,,,,,}一般View有三個構造器,我習慣讓只有一個參數的構造方法調用有兩個參數的構造方法,然後兩個的調用三個的,在有三個參數的構造方法裡使用TypeArray讀取自訂屬性,然後調用TypeArray的recycle()方法回收資源。
3,重寫onMeasure()
上網稍微搜尋一下,很快能只奧怎麼重寫onMeasure()方法,但是為什麼需要重寫onMeasure()方法這一點,倒是不好搜尋到。這個問題我記錄在另一篇文章。
android自訂View-------為什麼重寫onMeasure()以及怎麼重寫
我們在onMeasure()中此處主要做了兩件事,第一是計算View的大小,也就是它的長和寬,第二是記錄了白色色塊的起始位置,以及繪製按鈕的一些其他參數。需要注意的是,當你調用view的invalidate()方法進行重繪時,系統只會去調用onDraw()方法,但是如果有關於其他組件的大小、內容(比如TextView的setText())發生修改,都會觸發onMeasure(),所以在onMeasure()中計算的位置變數,要避免受到重新調用onMeasure()的影響。編碼過程遇到的一個bug就是因為這個原因。
4,重寫onDraw()
onDraw()部分比較容易,只需按照onMeasure()中計算出來的參數繪製三個圖層即可。
5,如果需要,重寫onTouch監聽
該部分也是比較容易,就是分別按照down,move,up三個事件計算位置,修改第二個圖層顏色的透明度,然後調用invaliate重繪View。在up事件中啟動一個線程,讓按鈕的滑動自動進行到結束。
6,在自己項目的布局檔案中,定義一個命名空間,使用屬性
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:slideswitch="http://schemas.android.com/apk/res/com.example.testlibs" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffffff" android:gravity="center_horizontal" android:orientation="vertical" android:padding="10dip" tools:context="com.example.testlibs.MainActivity" > <com.leaking.slideswitch.SlideSwitch android:id="@+id/swit" android:layout_width="150dip" android:layout_height="60dip" slideswitch:isOpen="true" slideswitch:shape="rect" slideswitch:themeColor="#ffee3a00" > </com.leaking.slideswitch.SlideSwitch></LinearLayout>
需要注意的是,上述程式碼片段的第二行,它引入了一個命名空間,其格式如下
xmlns:隨便起名字="http://schemas.android.com/apk/res/你的應用的包名"
注意最後一部分,是你使用這個自訂VIew的那個項目的應用程式套件名。
SlideSwitch.java完整代碼
完整代碼和測試例子都放到github了,https://github.com/Leaking/SlideSwitch
package com.leaking.slideswitch;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.RectF;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.support.v4.view.MotionEventCompat;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import com.example.slideswitch.R;public class SlideSwitch extends View {public static final int SHAPE_RECT = 1;public static final int SHAPE_CIRCLE = 2;private static final int RIM_SIZE = 6;private static final int COLOR_THEME = Color.parseColor("#ff00ee00");// 3 attributesprivate int color_theme;private boolean isOpen;private int shape;// varials of drawingprivate Paint paint;private Rect backRect;private Rect frontRect;private int alpha;private int max_left;private int min_left;private int frontRect_left;private int frontRect_left_begin = RIM_SIZE;private int eventStartX;private int eventLastX;private int diffX = 0;private SlideListener listener;public interface SlideListener {public void open();public void close();}public SlideSwitch(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);listener = null;paint = new Paint();paint.setAntiAlias(true);TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.slideswitch);color_theme = a.getColor(R.styleable.slideswitch_themeColor,COLOR_THEME);isOpen = a.getBoolean(R.styleable.slideswitch_isOpen, false);shape = a.getInt(R.styleable.slideswitch_shape, SHAPE_RECT);a.recycle();}public SlideSwitch(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SlideSwitch(Context context) {this(context, null);}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = measureDimension(280, widthMeasureSpec);int height = measureDimension(140, heightMeasureSpec);if (shape == SHAPE_CIRCLE) {if (width < height)width = height * 2;}setMeasuredDimension(width, height);initDrawingVal();}public void initDrawingVal(){int width = getMeasuredWidth();int height = getMeasuredHeight();backRect = new Rect(0, 0, width, height);min_left = RIM_SIZE;if (shape == SHAPE_RECT)max_left = width / 2;elsemax_left = width - (height - 2 * RIM_SIZE) - RIM_SIZE;if (isOpen) {frontRect_left = max_left;alpha = 255;} else {frontRect_left = RIM_SIZE;alpha = 0;}frontRect_left_begin = frontRect_left;}public int measureDimension(int defaultSize, int measureSpec) {int result;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);if (specMode == MeasureSpec.EXACTLY) {result = specSize;} else {result = defaultSize; // UNSPECIFIEDif (specMode == MeasureSpec.AT_MOST) {result = Math.min(result, specSize);}}return result;}@Overrideprotected void onDraw(Canvas canvas) {if (shape == SHAPE_RECT) {paint.setColor(Color.GRAY);canvas.drawRect(backRect, paint);paint.setColor(color_theme);paint.setAlpha(alpha);canvas.drawRect(backRect, paint);frontRect = new Rect(frontRect_left, RIM_SIZE, frontRect_left+ getMeasuredWidth() / 2 - RIM_SIZE, getMeasuredHeight()- RIM_SIZE);paint.setColor(Color.WHITE);canvas.drawRect(frontRect, paint);} else {// 畫圓形int radius;radius = backRect.height() / 2 - RIM_SIZE;paint.setColor(Color.GRAY);canvas.drawRoundRect(new RectF(backRect), radius, radius, paint);paint.setColor(color_theme);paint.setAlpha(alpha);canvas.drawRoundRect(new RectF(backRect), radius, radius, paint);frontRect = new Rect(frontRect_left, RIM_SIZE, frontRect_left+ backRect.height() - 2 * RIM_SIZE, backRect.height()- RIM_SIZE);paint.setColor(Color.WHITE);canvas.drawRoundRect(new RectF(frontRect), radius, radius, paint);}}@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = MotionEventCompat.getActionMasked(event);switch (action) {case MotionEvent.ACTION_DOWN:eventStartX = (int) event.getRawX();break;case MotionEvent.ACTION_MOVE:eventLastX = (int) event.getRawX();diffX = eventLastX - eventStartX;int tempX = diffX + frontRect_left_begin;tempX = (tempX > max_left ? max_left : tempX);tempX = (tempX < min_left ? min_left : tempX);if (tempX >= min_left && tempX <= max_left) {frontRect_left = tempX;alpha = (int) (255 * (float) tempX / (float) max_left);invalidateView();}break;case MotionEvent.ACTION_UP:int wholeX = (int) (event.getRawX() - eventStartX);frontRect_left_begin = frontRect_left;boolean toRight;toRight = (frontRect_left_begin > max_left / 2 ? true : false);if (Math.abs(wholeX) < 3) {toRight = !toRight;}moveToDest(toRight);break;default:break;}return true;}/** * draw again */private void invalidateView() {if (Looper.getMainLooper() == Looper.myLooper()) {invalidate();} else {postInvalidate();}}public void setSlideListener(SlideListener listener) {this.listener = listener;}public void moveToDest(final boolean toRight) {final Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {if (msg.what == 1) {listener.open();} else {listener.close();}}};new Thread(new Runnable() {@Overridepublic void run() {if (toRight) {while (frontRect_left <= max_left) {alpha = (int) (255 * (float) frontRect_left / (float) max_left);invalidateView();frontRect_left += 3;try {Thread.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}}alpha = 255;frontRect_left = max_left;isOpen = true;if (listener != null)handler.sendEmptyMessage(1);frontRect_left_begin = max_left;} else {while (frontRect_left >= min_left) {alpha = (int) (255 * (float) frontRect_left / (float) max_left);invalidateView();frontRect_left -= 3;try {Thread.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}}alpha = 0;frontRect_left = min_left;isOpen = false;if (listener != null)handler.sendEmptyMessage(0);frontRect_left_begin = min_left;}}}).start();}public void setState(boolean isOpen) {this.isOpen = isOpen;initDrawingVal();invalidateView();if(listener != null)if(isOpen == true){listener.open();}else{listener.close();}}public void setShapeType(int shapeType) {this.shape = shapeType;}}
android自訂View-------滑動按鈕