Android 自定义view之扇形菜单(上)

最近模仿猎豹清理大师中的快切功能,一个扇形菜单,分三个菜单,可以滑动切换,每个菜单可以长按编辑,添加,删除,排序等,同时工具菜单中有系统的各种功能开关设置,话不多说,先看效果:


就是这样,猎豹的提供了各种皮肤,比较炫酷,这里我们就先实现一个简单版本的,因为整个项目内容还是比较多的,所以分三个部分来讲述,第一个部分先介绍布局,绘制,第二部分介绍点击,滑动切换,长按编辑,拖动排序等,最后一部分介绍添加删除,工具菜单实现,添加打开关闭动画等细节补充。(由于项目代码比较多,都贴出来会很长,所以我们在文章中只贴核心代码部分,后面我会把整个项目的下载地址发出来,有需要的可以直接下载源码查看)。

首先我们看到这样的需求,先考虑如何实现,很明显这次的需求包含了自定义view,touch事件分发,手势判断,属性动画,以及基本的数学圆弧角度和坐标计算等等,对自定义view比较了解的应该还是比较简单的,那我们就来分层次分模块来实现,步步击破最终完成需求。

一、布局

首先从布局开始,因为自定义复杂view的时候,我们一般是要分多模块,多层次,一个view不要搞太多功能,这样能尽可能简化逻辑,降低难度,如果全部写在一个view中,太过复杂容易出问题,而且不好查找和修改,所以我们就来写布局,从布局上划分区域和模块,这里我们可以从圆的中心开始,分为center中心点,selecttext三个tab,menu内容这三个部分,当然最底层我们需要一个background层,同时也方便后面添加皮肤,四大模块分好了,再考虑每一个模块的实现细节,其中center里面就只有一个x我们可以直接把cener作为一个view,selecttext里面有三个textview,同时有一个选中的indicator,menu是有三个扇形区域,每个里面的布局是一样的,然后按照旋转角度设置就可以了,背景暂时就一个半透明,ok了解到这里我们就分解完毕,现在就开始上代码,先新建layout,我们就叫fan_menu_root_layout.xml内容如下:



    
    
        

        
        

        
        

        
    
    
        
        
        
        
    
    
好了,除了三个textview其他都是我们自定义的view,其中FanCenterView和FanBackgroundView是直接继承view,其他的都是继承viewgroup。然后每一个都重写三个方法,onMeasure,onLayout,onDraw,自定义三部曲不解释,布局到这里就基本完成了,下面我们就一个个的来绘制。

二、绘制

下面开始绘制,初始化:新建画笔,设置颜色,获取一些必要的宽高,把onDraw中用到的可以提前计算和提前准备的对象都准备好,然后就是测量:根据父组件的宽高和mode选择合适的宽高最后调用setMeasuredDimension设置具体宽高,再布局:view中的布局无非是根据l t r b这四个值来计算onDraw中需要的大小数据,viewgroup的话layout需要根据上下左右四个坐标计算child所在位置,然后调用child的layout,最后绘制:根据不同的效果在canvas上进行不同的绘制,一般就是drawCircle、drawArc、drawLines、drawcolor等等,ok,一个一个分析,先看FanCenterView

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
                : mViewWidth, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
                : mViewWidth);
    }

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

        mCenterX = (int) (getMeasuredHeight() * CENTER_TEXT_OFFSET);

        mCenterY = getMeasuredHeight() - mCenterX;
        mCenterX = isLeft() ? mCenterX : getMeasuredWidth() - mCenterX;

        int offsetTextX;
        int offsetTextY;

        offsetTextX = mCenterX + (isLeft() ? mTextOffset : -mTextOffset);
        offsetTextY = mCenterY - mTextOffset;

        mLines[0] = offsetTextX - mTextSize;
        mLines[1] = offsetTextY - mTextSize;
        mLines[2] = offsetTextX + mTextSize;
        mLines[3] = offsetTextY + mTextSize;

        mLines[4] = offsetTextX - mTextSize;
        mLines[5] = offsetTextY + mTextSize;
        mLines[6] = offsetTextX + mTextSize;
        mLines[7] = offsetTextY - mTextSize;

        afterOnMeasure();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawCircle(mCenterX, mCenterY, mCenterY, mBgPaint);
        canvas.drawLines(mLines, mTextPaint);
    }
}


onMeasure中根据根据宽高和mode来设置,其中mViewWidth是中心圆的宽,在dimens中定义的具体大小。onlayout主要是计算了中心圆的圆心坐标,其中圆心不是在右下角,是有一个偏移的,isLeft()是判断在左边还是右边,因为猎豹是左右都可以出,我们是根据悬浮按钮的位置来决定是左边还是右边的,根据这个值就有两种布局,所以细心的话可以发现,在布局中每个组件的宽度我都是设置了fill_parent,这样在onmeasure设置的其实只有高度是我们设置的,宽度是屏幕宽度,然后在onlayout中根据左右可以设置圆心位置,然后根据圆心计算了X号的四个点的位置,方便draw X。onDraw因为前面已经准备了所有的参数,所以只需要两行代码,画一个圆,再话线就ok了,其中画圆需要有渐变,画线设置宽和颜色等等就不介绍了,同理我们可以完成FanSelectTextLayout、FanMenuView、FanBackgroundView,就不一一介绍了,贴出关键代码:

FanSelectTextLayout:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        measureChildren(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
                : sizeWidth, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
                : sizeWidth);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);

            if (isLeft()) {
                if (child instanceof FanSelectTextIndicator) {
                    child.layout(l, b - mRadius, l + mRadius, b);
                } else {
                    child.layout(mTvPadding, b - child.getMeasuredHeight() / 2, mTvPadding + child.getMeasuredWidth(),
                            b + child.getMeasuredHeight() / 2);
                }

            } else {
                if (child instanceof FanSelectTextIndicator) {
                    child.layout(r - mRadius, b - mRadius, r, b);
                } else {
                    child.layout(r - mTvPadding - child.getMeasuredWidth(), b - child.getMeasuredHeight() / 2, r - mTvPadding,
                            b + child.getMeasuredHeight() / 2);
                }
            }
        }
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {

        if (child instanceof FanSelectTextIndicator) {
            return super.drawChild(canvas, child, drawingTime);
        }

        int count = getChildCount();
        int index = indexOfChild(child);
        int degree = 90 / (count);
        degree = isLeft() ? -degree : degree;
        canvas.save();
        canvas.rotate(degree * (index), isLeft() ? 0 : getWidth(), getHeight());
        boolean result = super.drawChild(canvas, child, drawingTime);
        canvas.restore();
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawCircle(isLeft() ? 0 : getWidth(), getHeight(), mRadius, mPaint);
    }

这里也是一样,不过区别就是onLayout中每个child需要计算具体的位置,我这里是把三个textview布局到了0度的位置,就是文字中心在水平线上的位置,三个重叠放(因为下面我重写了drawChild,然后把canvas旋转30度分别drawChild,所以布局就重叠的,根据角度不同画出来就是分开的),mTvPading是偏移右下角的距离包含了中心点(如果isLeft=true就是左下角),指示器的大小就是整个组件大小,指示器这里不做设置,因为指示器跟textview的中心重叠但是要可以三个来回切换放一起会变的复杂难控制,所以我们的原则是越简单越好。

FanMenuView

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        int tempSpec = MeasureSpec.makeMeasureSpec(mOuterRadius, MeasureSpec.EXACTLY);
        measureChildren(tempSpec, tempSpec);
        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
                : sizeWidth, (heightMode == MeasureSpec.EXACTLY) ? sizeWidth
                : sizeWidth);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);

            if (!(child instanceof FanMenuLayout)) {
                break;
            }

            if (isLeft()) {
                child.layout(0, getHeight() - mOuterRadius, mOuterRadius, getHeight());
            } else {
                child.layout(getWidth() - mOuterRadius, getHeight() - mOuterRadius, getWidth(), getHeight());
            }
        }

        afterOnMeasure();
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {

        int index = indexOfChild(child);

        float rotateDegree = getCurrentDegreeByChildIndex(index) + mOffsetDegree;
        rotateDegree = isLeft() ? rotateDegree : -rotateDegree;

        canvas.save();
        canvas.rotate(rotateDegree, isLeft() ? 0 : getWidth(), getHeight());
        boolean result = super.drawChild(canvas, child, drawingTime);
        canvas.restore();
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int width = isLeft() ? 0 : getWidth();
        int height = getHeight();

        canvas.drawCircle(width, height, mOuterRadius, mPaint);
        canvas.drawCircle(width, height, mInnerRadius, mAlphaPaint);
        canvas.drawCircle(width, height, mInnerRadius, mInnerPaint);
    }

FanMenuViw采用的原理类似,也是在drawChild中旋转画布然后drawChild来画三个child,onDraw中画了三个圆,其中两个做Xfermode来实现中间透明圆环。FanMenuBackground什么也没做就不贴代码了。


到此我们已经画出来中心点,三个tab和三个菜单,但是菜单上什么也没有。下面我们就添加内容

首先是添加指示器,指示器是一个小的扇形,带渐变的,位置根据当前选中的tab来改变,

FanSelectTextIndicator

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

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

        if (isLeft()) {
            mFanRectF.left = -mWidth;
            mFanRectF.right = mWidth;
            mFanRectF.top = 0;
            mFanRectF.bottom = 2 * mWidth;
        } else {
            mFanRectF.left = 0;
            mFanRectF.right = 2 * mWidth;
            mFanRectF.top = 0;
            mFanRectF.bottom = 2 * mWidth;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();

        canvas.rotate(mStartDegree, isLeft() ? 0 : mWidth, mWidth);
        float startDegree = isLeft() ? -DEFAULT_DEGREE - mFanDegree : 180 + DEFAULT_DEGREE;
        canvas.drawArc(mFanRectF, startDegree, mFanDegree, true, mPaint);

        canvas.restore();
    }

这里我们以中心点右下角为圆心,画一个mFanDegree为90 / 4度的弧,mStartDegree根据选中的项来设置不同的值从而达到旋转的目的。看效果图指示器在达到中心点前就已经消失了,而我们这里什么都没有处理,其实是在mPaint中,我们设置了渐变,最后一种颜色是透明,所以在达到中心点前就看不到了

    public void initView(Context context) {
        Resources rs = context.getResources();

        // color3是透明的,
        int color1 = rs.getColor(R.color.fan_menu_text_indicator_bg_color1);
        int color2 = rs.getColor(R.color.fan_menu_text_indicator_bg_color2);
        int color3 = rs.getColor(R.color.fan_menu_text_indicator_bg_color3);
        mWidth = rs.getDimensionPixelSize(R.dimen.fan_menu_select_text_bg_radius);

        mColors = new int[4];
        mColors[0] = color1;
        mColors[1] = color2;
        mColors[2] = color3;
        mColors[3] = color3;

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);

        mFanDegree = 90 / 4;
    }

    protected void afterOnMeasure() {

        LinearGradient lg = new LinearGradient(isLeft() ? mWidth : 0, 0,
                isLeft() ? 0 : mWidth, mWidth, mColors, new float[]{0, 0.5f, 0.7f, 1}, Shader.TileMode.REPEAT);
        mPaint.setShader(lg);
    }

设置了渐变的方向和渐变间距,从而达到效果图类似的效果。


添加完了指示器我们就来添加菜单布局,三个菜单布局是一样的,仔细看我们的layout会发现我们使用了自定义属性,

fanmenu:menuType="3"

这个是我们来区分到底是哪个菜单项,FanMenuLayout,这个代码比较多,我们先不管其他的,只看绘制


package com.jeden.fanmenu.view;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

import com.jeden.fanmenu.R;
import com.jeden.fanmenu.bean.AppInfo;
import com.jeden.fanmenu.util.FanMenuViewTools;
import com.jeden.fanmenu.common.model.ContentProvider;
import com.jeden.fanmenu.util.FanLog;
import com.jeden.fanmenu.view.base.CardState;
import com.jeden.fanmenu.view.base.CommonPositionViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by jeden on 2017/3/12.
 */

public class FanMenuLayout extends CommonPositionViewGroup {

    public static final String TAG = FanMenuLayout.class.getSimpleName();

    private static final String ADD_VIEW_TAG = "MENU_LAYOUT_ITEM_ADD_TAG";
    private int mMenuType = CardState.CARD_STATE_RECENTLY;
    private boolean mLongClickable;

    private int mItemHalfWidth;
    private int mItemInnerRadius;
    private int mItemOuterRadius;

    private long mLastTime;
    private int mTouchSlop;

    private Runnable mLongClickRunnable = new LongClickRunnable();
    protected Vibrator mVibrator;
    protected boolean mIsEditModel;
    private FanMenuItemView mDownSelectChild;

    private boolean mIsDeleting = false;
    private int[] mLastSlot = new int[3];
    private FanMenuItemView[] mLastSlotView = new FanMenuItemView[9];
    private Rect[] mDragIndexPoint = new Rect[9];
    private boolean mIsDragging = false;
    private int[] mTempP = new int[2];

    private Drawable mCache;
    private boolean mRotating = false;
    private int mBackgroundColor;

    private static Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    public FanMenuLayout(Context context) {
        super(context);
    }

    public FanMenuLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        initView(context, attrs);
    }

    public FanMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initView(context, attrs);
    }

    public void initView(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FanMenu);
        mMenuType = ta.getInteger(R.styleable.FanMenu_menuType, -1);
        mLongClickable = ta.getBoolean(R.styleable.FanMenu_longClickable, false);
        ta.recycle();

        addAllItemView();

        Resources rs = getResources();
        mItemHalfWidth = rs.getDimensionPixelSize(R.dimen.fan_menu_item_half_width);
        mItemInnerRadius = rs.getDimensionPixelSize(R.dimen.fan_menu_item_inner_radius);
        mItemOuterRadius = rs.getDimensionPixelSize(R.dimen.fan_menu_item_outer_radius);
        mBackgroundColor = rs.getColor(R.color.fan_menu_common_transparent_color);

        mTouchSlop = FanMenuViewTools.dip2px(context, 5);

        setPersistentDrawingCache(PERSISTENT_ALL_CACHES);
        setDrawingCacheBackgroundColor(0xFF000000);
    }

    public void addAllItemView() {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        FanMenuItemView itemView;

        List tempAppInfo = getData();

        for (AppInfo info : tempAppInfo) {
            itemView = new FanMenuItemView(getContext());
            itemView.setToolboxModel(mMenuType == CardState.CARD_STATE_TOOLBOX);
            itemView.setTitle(info.getAppLabel());
            itemView.setItemIcon(info.getAppIcon());
            itemView.setTag(info);
            this.addView(itemView);
            itemView.setOnTouchListener(mMyViewTouchListener);
        }

        addViewIfNeed(inflater);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        int tempSpec = MeasureSpec.makeMeasureSpec(mItemHalfWidth * 2, MeasureSpec.EXACTLY);
        measureChildren(tempSpec, tempSpec);
        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
                : sizeWidth, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
                : sizeHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        count = count > 9 ? 9 : count;

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);

            mTempP[0] = isLeft() ? 0 : getWidth();
            mTempP[1] = getHeight();

            getItemCenterPoint(i, count, mTempP);
            child.layout(mTempP[0] - mItemHalfWidth, mTempP[1] - mItemHalfWidth, mTempP[0] + mItemHalfWidth, mTempP[1] + mItemHalfWidth);
        }
    }

    public void addViewIfNeed(LayoutInflater inflater) {
        if (mMenuType == CardState.CARD_STATE_FAVORITE || mMenuType == CardState.CARD_STATE_TOOLBOX) {
            FrameLayout addView = (FrameLayout) inflater.inflate(R.layout.fan_menu_item_add_layout, null);
            addView.setTag(ADD_VIEW_TAG);
            addView.setOnTouchListener(mMyViewTouchListener);
            this.addView(addView);
        }
    }

    public double generateDegreePerItem(int index, int count) {
        if (count < 0 || count > 9)
            return 0;

        double degree = Math.PI / 2;
        if (count < 5) {
            return degree / (2 * count) * (2 * index + 1);
        } else {
            if (index < 4) {
                return degree / 8 * (2 * index + 1);
            }
            return (degree / (2 * (count - 4))) * (2 * (index - 4) + 1);
        }
    }

    public void getItemCenterPoint(int index, int count, int[] p) {

        int radius = index >= 4 ? mItemOuterRadius : mItemInnerRadius;

        double degree = generateDegreePerItem(index, count);

        int x = (int) (Math.sin(degree) * radius);
        p[1] -= (int) (Math.cos(degree) * radius);
        p[0] += (isLeft() ? x : -x);
    }

    public List getData() {
        List tempAppInfo = null;
        switch (mMenuType) {
            case CardState.CARD_STATE_FAVORITE:
                tempAppInfo = ContentProvider.getInstance().getFavorite();
                break;
            case CardState.CARD_STATE_RECENTLY:
                tempAppInfo = ContentProvider.getInstance().getRecently();
                break;
            case CardState.CARD_STATE_TOOLBOX:
                tempAppInfo = ContentProvider.getInstance().getToolBox();
                break;
            default:
                break;
        }

        return tempAppInfo;
    }
    ...
}

好了,这个类是我们比较核心的类,里面包括了每个菜单的绘制,点击,编辑,拖动,排序,添加和删除等等功能,我们这里省略其他部分,只看绘制的,其他的后面我们会讲,这里我们要先考虑一下我们的数据结构了,我们要绘制菜单里的每一项,而每一项其实就是一个app的icon,名称,包名等信息,我们这里自定义了一个ContentProvider的数据类,它为我们提供了三个listview,每一个都是每个菜单项包含的菜单数,如果是工具和常用应用还要判断是否满9个,不满的话添加一个+号项,然后在initview中我们获取了两个圆的半径(两排菜单排列是在两个圆环上面的),halfwidth是每一个项的宽度的一半,因为我们layout的时候是根据有多少项,然后在内圆上开始排列,内圆最多有四个,排满就排外圆,每个圆内是等分排列(就是如果只有两个,那么内圆分两份,算出两个点的坐标,然后把两个view的中心放在这两个点上),其中计算点的坐标就牵涉到了弧度和坐标x,y的转换,也比较简单,看代码应该容易理解。这里我们又牵涉到了一个FanMenuItemView,这个就是每一个项的布局了,这里原来我是用xml定义的一个layout,里面用的textview和两个imageview,但是后来考虑效率问题改成了直接绘制text和drawable

FanMenuItemView

package com.jeden.fanmenu.view;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;

import com.jeden.fanmenu.R;
import com.jeden.fanmenu.bean.AppInfo;
import com.jeden.fanmenu.util.FanMenuViewTools;
import com.jeden.fanmenu.common.tools.SwipeTools;
import com.jeden.fanmenu.common.tools.SwipeView;
import com.jeden.fanmenu.common.tools.ToolboxHelper;
import com.jeden.fanmenu.view.base.CommonPositionView;

/**
 * Created by jeden on 2017/3/12.
 */

public class FanMenuItemView extends CommonPositionView implements SwipeView {
    private static final String TAG = FanMenuItemView.class.getSimpleName();
    private boolean mToolboxModel = false;
    private int mWidth;
    private int mHeight;
    private int mIconW;
    private int mIconH;
    private int mIconL;
    private int mIconT;
    private float mTitleT;
    private float mTitleL;
    private int mDelW;
    private int mDelR;
    private Drawable mIcon;
    private String mTitle;
    private boolean mDelShow;
    private Paint mTitlePaint;
    private Drawable mDel;

    public FanMenuItemView(Context context) {
        super(context);
        initView(context);
    }

    public FanMenuItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public FanMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    private void initView(Context context) {
        Resources rs = context.getResources();
        mWidth = rs.getDimensionPixelSize(R.dimen.fan_menu_item_title_width);
        mIconW = rs.getDimensionPixelSize(R.dimen.fan_menu_item_icon_width);
        mDelW = rs.getDimensionPixelSize(R.dimen.fan_menu_item_close_width);
        int marginDP = FanMenuViewTools.dip2px(context, 3);

        mDelR = mDelW + marginDP;
        mHeight = mWidth;
        mIconH = mIconW;
        mIconT = marginDP;
        mIconL = (mWidth - mIconW) / 2;
        mTitleT = mHeight;
        mTitleL = 0;

        mTitlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTitlePaint.setColor(rs.getColor(R.color.fan_menu_common_white_color));
        mTitlePaint.setTextSize(rs.getDimensionPixelSize(R.dimen.fan_menu_item_title_size));
        Paint.FontMetrics fontMetrics = mTitlePaint.getFontMetrics();
        float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
        float offY = fontTotalHeight / 2 - fontMetrics.bottom;
        mTitleT -= offY;

        mDel = rs.getDrawable(R.drawable.fan_item_close);
        mDel.setBounds(marginDP, 0, mDelR, mDelW);
    }

    public void showDelBtn() {
        mDelShow = true;
        invalidate();
    }

    public void hideDelBtn() {
        mDelShow = false;
        invalidate();
    }

    public void setTitle(String title) {
        mTitle = title;
        mTitle = mTitle.trim();
        float titleW = mTitlePaint.measureText(mTitle);
        if (titleW > mWidth) {
            int subIndex = mTitlePaint.breakText(mTitle, 0, mTitle.length(), true, mWidth, null);
            mTitle = mTitle.substring(0, subIndex) + "...";
            titleW = mWidth;
        }

        mTitleL = (mWidth - titleW) / 2;
        invalidate();
    }

    public String getTitle() {
        return mTitle;
    }

    public void setItemIcon(Drawable icon) {
        mIcon = icon.getConstantState().newDrawable();

        mIcon.setBounds(mIconL, mIconT, mIconL + mIconW, mIconT + mIconH);
        invalidate();
    }

    public Rect getDeleteRect() {
        return new Rect(0, 0, mDelR, mDelR);
    }

    public void setToolboxModel(boolean toolboxModel) {
        mToolboxModel = toolboxModel;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
                : mWidth, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
                : mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mIcon.draw(canvas);
        canvas.drawText(mTitle, mTitleL, mTitleT, mTitlePaint);
        if (mDelShow)
            mDel.draw(canvas);
    }

    @Override
    public void setTag(Object tag) {
        super.setTag(tag);
        if (tag instanceof AppInfo) {
            AppInfo appInfo = (AppInfo) tag;
            if (appInfo.getIntent() == null) {
                SwipeTools tools = ToolboxHelper.checkSwipeTools(getContext(), appInfo);
                if (tools != null) {
                    tools.bindSwipeView(getContext(), this);
                }
            }
        }
    }
}

这里都是最基本的绘制操作,其中需要注意的是setItemIcon的时候需要把drawable拷贝一份,原因是要改变drawable的bounds信息,不能把我们的数据结构中的drawable改变不然如果其他菜单里也有该项位置就会发生偏移,好了,到现在我们基本上绘制就结束了,现在我们就可以把rootview添加到windowmanager中了,不出意外就可以看到效果了。那今天就到这里,下一篇我们介绍如何让菜单动起来。



你可能感兴趣的:(android)