Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義View之MenuItemView

自定義View之MenuItemView

編輯:關於Android編程

著手開發一款應用的時候,設置或者菜單頁面是可能需要的,但是,那重復的布局會很令人苦惱。新手可能會一項項的重復繪制,有經驗的你或許會用到include,或者用到組合控件。除了以上的方法之外,閒來無事,寫了一個通用的View(MenuItemView)。此view暫時可以展示兩種功能,一是通用的項,另一種是帶開關的項,截圖如下:
這裡寫圖片描述

1.自定義屬性:attrs.xml<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">

2.MenuItemView.java源碼:

package com.dandy.weight;

/**
 * Created by Administrator on 2016/7/20.
 */
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.dandy.album.R;

/**
 * 設置,菜單中的布局項
 * 默認控件是縱向居中顯示,所有paddingTop和paddingBottom在這沒有作用
 * Created by dandy on 2016/4/14.
 */
public class MenuItemView extends View{

    private static final String TAG = "MenuItemView";

    /**默認控件的高**/
    private static final int HEIGHT_DEFAULT = 50;

    /**文字繪制時默認大小**/
    private static final int TEXTSZIE_DEFAULT = 14;

    /**尾部箭頭圖標大小**/
    private static final int ARROW_SIZE = 13;

    /***SwitchButton默認寬*/
    private static final int SWITCHBUTTON_WIDTH = 50;

    /***SwitchButton默認高*/
    private static final int SWITCHBUTTON_HEIGHT = 28;

    private static  final long DELAYDURATION = 10;

    /**頭標**/
    private Drawable headerDrawable;

    /**頭標大小,寬高一致**/

    private int headDrawableSize = -1;

    /**頭標寬**/
    private int headerDrawableWidth;
    /**頭標高**/
    private int headerDrawableHeight;

    /**繪制頭標時,畫布在Y軸的繪制偏移量**/
    private float headerDrawableStartDrawY;

    /**文字與圖片間的距離**/
    private int drawablePadding = 0;

    /**頭部文字提示**/
    private String textHeader;

    /**文字顏色**/
    private int textHeaderColor = Color.parseColor("#5a5a5a");

    /**文字大小**/
    private int textSize = -1;

    /**繪制文字的畫筆**/
    private Paint textPaint;

    /** 尾部 > 圖片**/
    private Drawable arrowDrawable;

    /**尾部 > 大小**/
    private int arrowSize = -1;

    /** > 繪制的X軸偏移量**/
    private float arrowStartDrawX;

    /** > 繪制的Y軸偏移量**/
    private float arrowStartDrawY;

    /**尾部寬**/
    private int arrowDrawableWidth;
    /**尾部高**/
    private int arrowDrawableHeight;

    /**繪制arrow畫筆**/
    private Paint arrowPaint;

    /**arrowPaint 顏色**/
    private int arrowColor = Color.parseColor("#5a5a5a");

    private DisplayMetrics dm;

    /*以下是繪制SwitchButton所用到的參數*/

    private ItemType itemType = ItemType.NORMAL;

    /**默認寬**/
    private  int toggleWidth = -1;

    /**默認高**/
    private int toggleHeight = -1;

    /**開啟顏色**/
    private int onColor = Color.parseColor("#4ebb7f");
    /**關閉顏色**/
    private int offColor = Color.parseColor("#dadbda");
    /**灰色帶顏色**/
    private int areaColor = Color.parseColor("#dadbda");
    /**手柄顏色**/
    private int handlerColor = Color.parseColor("#ffffff");
    /**邊框顏色**/
    private int borderColor = offColor;
    /**開關狀態**/
    private boolean toggleOn = false;
    /**邊框寬**/
    private int borderWidth = 2;
    /**縱軸中心**/
    private float centerY;
    /**按鈕水平方向開始、結束的位置**/
    private float startX,endX;
    /**手柄x軸方向最小、最大值**/
    private float handlerMinX,handlerMaxX;
    /**手柄大小**/
    private int handlerSize;
    /**手柄在x軸的坐標位置**/
    private float handlerX;
    /**關閉時內部灰色帶寬度**/
    private float areaWidth;
    /**是否使用動畫效果**/
    private boolean animate = true;
    /**是否默認處於打開狀態**/
    private boolean defaultOn = true;
    /**按鈕半徑**/
    private float radius;

    /**整個switchButton的區域**/
    private RectF toggleRectF = new RectF();

    /**繪制switchButton的畫筆**/
    private Paint togglePaint;

    private OnToggleChangedListener mListener;

    private double currentDelay;

    private float downX = 0;

    /**switchButton在X軸繪制的偏移量**/
    private float switchButtonDrawStartX;

    /**switchButton在Y軸繪制的偏移量**/
    private float switchButtonDrawStartY;

    /**分割線,默認在底部繪制**/
    private Drawable dividerr;

    /**分割線繪制的寬**/
    private int dividerWidth = 2;

    /**是否需要繪制分割線**/
    private boolean dividerVisibilty = true;

    private boolean isPressed = false;

    public MenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setup(attrs);
    }
    public MenuItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(attrs);
    }

    /**
     * 初始化控件,獲取相關的控件屬性
     * @param attrs
     */
    private void setup(AttributeSet attrs){

        dm = Resources.getSystem().getDisplayMetrics();

        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MenuItemView);
        if(typedArray != null){
            int count = typedArray.getIndexCount();
            for(int i = 0;i < count;i++){
                int attr = typedArray.getIndex(i);
                switch (attr){
                    case R.styleable.MenuItemView_headerDrawable:
                        headerDrawable = typedArray.getDrawable(attr);
                        break;
                    case R.styleable.MenuItemView_headerSize:
                        headDrawableSize = typedArray.getDimensionPixelSize(attr,headDrawableSize);
                        break;
                    case R.styleable.MenuItemView_drawPadding:
                        drawablePadding = typedArray.getDimensionPixelSize(attr, drawablePadding);
                        break;
                    case R.styleable.MenuItemView_textHeader:
                        textHeader = typedArray.getString(attr);
                        break;
                    case R.styleable.MenuItemView_textColor:
                        textHeaderColor = typedArray.getColor(attr, textHeaderColor);
                        break;
                    case R.styleable.MenuItemView_textSize:
                        textSize = typedArray.getDimensionPixelSize(attr, textSize);
                        break;
                    case R.styleable.MenuItemView_arrowDrawable:
                        arrowDrawable = typedArray.getDrawable(attr);
                        break;
                    case R.styleable.MenuItemView_arrowSize:
                        arrowSize = typedArray.getDimensionPixelSize(attr, arrowSize);
                        break;
                    case R.styleable.MenuItemView_arrowColor:
                        arrowColor = typedArray.getColor(attr, arrowColor);
                        break;
                    case R.styleable.MenuItemView_onColor:
                        onColor = typedArray.getColor(attr, onColor);
                        break;
                    case R.styleable.MenuItemView_offColor:
                        borderColor = offColor = typedArray.getColor(attr,offColor);
                        break;
                    case R.styleable.MenuItemView_areaColor:
                        areaColor = typedArray.getColor(attr, areaColor);
                        break;
                    case R.styleable.MenuItemView_handlerColor:
                        handlerColor = typedArray.getColor(attr, handlerColor);
                        break;
                    case R.styleable.MenuItemView_bordeWidth:
                        borderWidth = typedArray.getColor(attr, borderWidth);
                        break;
                    case R.styleable.MenuItemView_animate:
                        animate = typedArray.getBoolean(attr, animate);
                        break;
                    case R.styleable.MenuItemView_defaultOn:
                        defaultOn = typedArray.getBoolean(attr, defaultOn);
                        break;
                    case R.styleable.MenuItemView_itemType:
                        itemType = ItemType.getValue(typedArray.getInt(attr, ItemType.NORMAL.ordinal()));
                        break;
                    case R.styleable.MenuItemView_toggleWidth:
                        toggleWidth = typedArray.getDimensionPixelOffset(attr, toggleWidth);
                        break;
                    case R.styleable.MenuItemView_toggleHeight:
                        toggleHeight = typedArray.getDimensionPixelOffset(attr, toggleHeight);
                        break;
                    case R.styleable.MenuItemView_dividerr:
                        dividerr = typedArray.getDrawable(attr);
                        break;
                    case R.styleable.MenuItemView_dividerWidth:
                        dividerWidth = typedArray.getDimensionPixelOffset(attr,dividerWidth);
                        break;
                    case R.styleable.MenuItemView_dividerVisibilty:
                        dividerVisibilty = typedArray.getBoolean(attr,dividerVisibilty);
                        break;
                }
            }
            typedArray.recycle();
        }

        if(textSize == -1){
            textSize = applyDimension(TEXTSZIE_DEFAULT);
        }

        /**
         * 初始化文字畫筆
         */
        textPaint = new Paint();
        textPaint.setColor(textHeaderColor);
        textPaint.setTextSize(textSize);
        textPaint.setAntiAlias(true);

        if(itemType == ItemType.TOGGLE){

            if(toggleWidth == -1){
                toggleWidth  = applyDimension(SWITCHBUTTON_WIDTH);
            }

            if(toggleHeight == -1){
                toggleHeight = applyDimension(SWITCHBUTTON_HEIGHT);
            }

            togglePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            togglePaint.setStyle(Paint.Style.FILL);
            togglePaint.setStrokeCap(Paint.Cap.ROUND);
            togglePaint.setAntiAlias(true);

            if(defaultOn){
                currentDelay = 1;
                toggleOn();
            }else{
                currentDelay = 0;
                toggleOff();
            }
        }else if(itemType == ItemType.ARROW){
            if(arrowSize == -1){
                arrowSize = applyDimension(ARROW_SIZE);
            }
            arrowPaint = new Paint();
            arrowPaint.setColor(arrowColor);
            arrowPaint.setAntiAlias(true);
            arrowPaint.setStrokeWidth(3.0f);
        }

        /**
         * 設置默認背景色
         */
        if(getBackground() == null){
            setBackgroundColor(Color.parseColor("#FFFFFF"));
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

        if(headerDrawable != null){
            if(headDrawableSize >= 0 ){
                headerDrawableWidth = headerDrawableHeight = headDrawableSize;
            }else{
                headerDrawableWidth = headerDrawable.getIntrinsicWidth();
                headerDrawableHeight = headerDrawable.getIntrinsicHeight();
            }
            Log.i(TAG,"[onLayout] headerDrawableWidth = "+headerDrawableWidth+",headerDrawableHeight = "+headerDrawableHeight);

            headerDrawableStartDrawY = (getHeight() - headerDrawableHeight)/ 2.0f;
            Log.i(TAG, "[onLayout] headerDrawableStartDrawY = " + headerDrawableStartDrawY);
        }

        if(itemType == ItemType.ARROW){
            if(arrowDrawable != null){
                arrowDrawableWidth = arrowDrawable.getIntrinsicWidth();
                arrowDrawableHeight = arrowDrawable.getIntrinsicHeight();
            }
            if(arrowSize >= 0){
                arrowDrawableWidth = arrowDrawableHeight = arrowSize;
            }
            Log.i(TAG, "[onLayout] arrowDrawableWidth = " + arrowDrawableWidth + ",arrowDrawableHeight = " + arrowDrawableHeight);

            arrowStartDrawX = getWidth() - getPaddingRight() - arrowDrawableWidth;
            arrowStartDrawY = (getHeight() - arrowDrawableHeight) / 2.0f;
            Log.i(TAG, "[onLayout] arrowStartDrawX = " + arrowStartDrawX + ",arrowStartDrawY = " + arrowStartDrawY);
        }else if(itemType == ItemType.TOGGLE){
            radius = Math.min(toggleWidth,toggleHeight) * 0.5f;
            centerY = radius;
            startX = centerY;
            endX = toggleWidth - radius;
            handlerMinX = startX + borderWidth;
            handlerMaxX = endX - borderWidth;
            handlerSize = toggleHeight - 4*borderWidth;
            handlerX = toggleOn?handlerMaxX:handlerMinX;
            areaWidth = 0;

            switchButtonDrawStartX = getWidth() - getPaddingRight() - toggleWidth;
            switchButtonDrawStartY = (getHeight() - toggleHeight) / 2.0f;
            Log.i(TAG, "[onLayout] switchButtonDrawStartX = " + switchButtonDrawStartX + ",switchButtonDrawStartY = " + switchButtonDrawStartY);
        }

        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        /**計算寬**/
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
            width = applyDimension(-1);
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
        }

        /**計算高**/
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) {
            height = applyDimension(HEIGHT_DEFAULT);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        }

        Log.i(TAG, "[onMeasure] width = " + width + ",height = " + height);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        Log.i(TAG, "[onDraw] start draw!");

        if(headerDrawable != null){
            drawHeaderDrawable(canvas);
        }

        drawHeaderText(canvas);

        if (itemType == ItemType.ARROW) {
            drawFooterDrawable(canvas);
        }else if(itemType == ItemType.TOGGLE){
            drawSwitchButton(canvas);
        }

        if(dividerVisibilty){
            drawDivider(canvas);
        }

        super.onDraw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                isPressed = true;
                if (itemType != ItemType.TOGGLE){
                    postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            MenuItemView.this.setPressed(isPressed);
                        }
                    }, ViewConfiguration.getTapTimeout());
                }else{
                    downX = event.getX();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                isPressed = false;
                this.setPressed(false);
                break;
            case MotionEvent.ACTION_UP:
                if(itemType != ItemType.TOGGLE){
                    performClick();
                    postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            MenuItemView.this.setPressed(false);
                        }
                    }, ViewConfiguration.getTapTimeout());
                }else if(downX >= switchButtonDrawStartX){//為了擴大switchButton的響應區域
                    toggle();
                }
                break;
            default:
                isPressed = false;
                this.setPressed(false);
                break;
        }

        return true;
    }

    /**
     * 繪制頭標
     * @param canvas
     */
    private void drawHeaderDrawable(Canvas canvas){
        if(headerDrawable == null){
            throw new NullPointerException("headerDrawable was setted null !");
        }
        canvas.save();
        canvas.translate(getPaddingLeft(), headerDrawableStartDrawY);
        headerDrawable.setBounds(0, 0, headerDrawableWidth, headerDrawableHeight);
        headerDrawable.draw(canvas);
        canvas.restore();
    }

    /**
     * 繪制頭部文字提示
     * @param canvas
     */
    private void drawHeaderText(Canvas canvas){
        canvas.save();
        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        RectF targetRect = new RectF(getPaddingLeft() + headerDrawableWidth + drawablePadding,0,getWidth(),getHeight());
        int baseLine =  (int)((targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2);
        canvas.translate(getPaddingLeft() + headerDrawableWidth + drawablePadding, baseLine);
        canvas.drawText(textHeader, 0, 0, textPaint);
        canvas.restore();
    }

    /**
     * 繪制尾部指示圖標
     *
     * @param canvas
     */
    private void drawFooterDrawable(Canvas canvas){
        canvas.save();
        canvas.translate(arrowStartDrawX, arrowStartDrawY);
        if(arrowDrawable != null){
            arrowDrawable.setBounds(0, 0, arrowDrawableWidth, arrowDrawableHeight);
            arrowDrawable.draw(canvas);
        }else{
            /**
             * 自行繪制arrow圖標,繪制規則是:X軸坐標在arrow中間位置開始繪制,Y軸繪制不變
             */
            final float centerY = arrowDrawableHeight / 2.0f;
            final float lineStartX = arrowDrawableWidth / 2.0f;
            final float lineEndX = arrowDrawableWidth;
            canvas.drawLine(lineStartX,0,lineEndX,centerY,arrowPaint);
            canvas.drawLine(lineStartX,arrowDrawableHeight,lineEndX,centerY,arrowPaint);
        }
        canvas.restore();
    }

    /**
     * 繪制switchButton
     * @param canvas
     */
    private void drawSwitchButton(Canvas canvas){
        canvas.save();
        canvas.translate(switchButtonDrawStartX,switchButtonDrawStartY);
        /**繪制整個按鈕**/
        toggleRectF.set(0, 0, toggleWidth, toggleHeight);
        togglePaint.setColor(borderColor);
        canvas.drawRoundRect(toggleRectF, radius, radius,togglePaint);

        /**繪制關閉灰色區域**/
        if(areaWidth > 0 ){
            final float cy = areaWidth * 0.5f;
            toggleRectF.set(handlerX - cy, centerY - cy, endX + cy, centerY + cy);
            togglePaint.setColor(offColor);
            canvas.drawRoundRect(toggleRectF,cy,cy,togglePaint);
        }

        /**繪制手柄**/
        final float handlerRadius = handlerSize * 0.5f;
        toggleRectF.set(handlerX - handlerRadius, centerY - handlerRadius, handlerX + handlerRadius, centerY + handlerRadius);
        togglePaint.setColor(handlerColor);
        canvas.drawRoundRect(toggleRectF, handlerRadius, handlerRadius, togglePaint);
        canvas.restore();
    }

    /**
     * 繪制分割線
     * @param canvas
     */
    private void drawDivider(Canvas canvas){
        canvas.save();
        canvas.translate(getPaddingLeft(), getHeight() - dividerWidth);
        if(dividerr == null){
            ShapeDrawable divider = new ShapeDrawable(new RectShape());
            divider.getPaint().setStrokeWidth(dividerWidth);
            divider.getPaint().setAntiAlias(true);
            divider.getPaint().setColor(Color.parseColor("#CCCCCC"));
            divider.setBounds(0, 0, getWidth(), dividerWidth);
            divider.draw(canvas);

        }else{
            dividerr.setBounds(0, 0, getWidth(),dividerWidth);
            dividerr.draw(canvas);
        }
        canvas.restore();
    }

    /**
     * 開關狀態切換
     */
    public void toggle(){
        toggle(animate);
    }
    /**
     * 開關狀態切換
     * @param animate
     */
    public void toggle(boolean animate){
        toggleOn = !toggleOn;
        takeEffect(animate);
    }

    /**
     * 開啟狀態
     */
    public void toggleOn(){
        toggleOn(animate);
    }
    /**
     * 開啟狀態
     * @param animate
     */
    public void toggleOn(boolean animate){
        toggleOn = true;
        takeEffect(animate);
    }

    /**
     * 關閉狀態
     */
    public void toggleOff(){
        toggleOff(animate);
    }
    /**
     * 關閉狀態
     * @param animate
     */
    public void toggleOff(boolean animate){
        toggleOn = false;
        takeEffect(animate);
    }

    /**
     * 開始處理狀態切換
     * @param animate
     */
    private void takeEffect(boolean animate){
        if(mListener != null){
            mListener.onToggle(toggleOn);
        }
        if(animate){
            postDelayed(toggleRunnable, DELAYDURATION);
        }else {
            caculateEffect(toggleOn ? 1 : 0);
        }
    }

    /**
     * 時時計算
     * @param value
     */
    private void caculateEffect(double value){

        handlerX = (float)mapValueFromRangeToRange(value,0,1.0,handlerMinX,handlerMaxX);

        areaWidth = (float)mapValueFromRangeToRange(1.0-value,0,1.0,10,handlerSize);

        final int fb = Color.blue(onColor);
        final int fr = Color.red(onColor);
        final int fg = Color.green(onColor);

        final int tb = Color.blue(offColor);
        final int tr = Color.red(offColor);
        final int tg = Color.green(offColor);

        int sb = (int) mapValueFromRangeToRange(1.0 - value, 0, 1.0, fb, tb);
        int sr = (int) mapValueFromRangeToRange(1.0 - value, 0, 1.0, fr, tr);
        int sg = (int) mapValueFromRangeToRange(1.0 - value, 0, 1.0, fg, tg);

        sb = clamp(sb, 0, 255);
        sr = clamp(sr, 0, 255);
        sg = clamp(sg, 0, 255);

        borderColor = Color.rgb(sr, sg, sb);

        postInvalidate();
    }

    private int clamp(int value, int low, int high) {
        return Math.min(Math.max(value, low), high);
    }
    /**
     * Map a value within a given range to another range.
     * @param value the value to map
     * @param fromLow the low end of the range the value is within
     * @param fromHigh the high end of the range the value is within
     * @param toLow the low end of the range to map to
     * @param toHigh the high end of the range to map to
     * @return the mapped value
     */
    private  double mapValueFromRangeToRange(
            double value, double fromLow, double fromHigh,
            double toLow, double toHigh) {
        double fromRangeSize = fromHigh - fromLow;
        double toRangeSize = toHigh - toLow;
        double valueScale = (value - fromLow) / fromRangeSize;
        return toLow + (valueScale * toRangeSize);
    }

    private final Runnable toggleRunnable = new Runnable() {
        @Override
        public void run() {
            if(toggleOn){
                if(currentDelay <= 1){
                    caculateEffect(currentDelay);
                    postDelayed(toggleRunnable,DELAYDURATION);
                    currentDelay = currentDelay + 0.1;
                }else{
                    currentDelay = 1;
                }
            }else{
                if(currentDelay >= 0){
                    caculateEffect(currentDelay);
                    postDelayed(toggleRunnable, DELAYDURATION);
                    currentDelay = currentDelay - 0.1;
                }else{
                    currentDelay = 0;
                }
            }
        }
    };

    /**
     * px2dp
     * @param value
     */
    private int applyDimension(float value){
        return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,value,dm);
    }

    /**
     * 類型
     */
    private enum ItemType{

        NORMAL, ARROW, TOGGLE;

        public static ItemType getValue(int index){
            for(ItemType type:values()){
                if(type.ordinal() == index){
                    return type;
                }
            }
            return NORMAL;
        }
    }

    /**
     * 設置開關監聽
     */
    public void setOnToggleChangedlistener(OnToggleChangedListener listener){
        this.mListener = listener;
    }
    /**
     * 開關狀態監聽
     */
    public interface OnToggleChangedListener{
        /**
         * 是否開啟
         * @param on
         */
        void onToggle(boolean on);
    }
}

3.布局就不說了,和正常的View使用就是了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved