最近模仿猎豹清理大师中的快切功能,一个扇形菜单,分三个菜单,可以滑动切换,每个菜单可以长按编辑,添加,删除,排序等,同时工具菜单中有系统的各种功能开关设置,话不多说,先看效果:
就是这样,猎豹的提供了各种皮肤,比较炫酷,这里我们就先实现一个简单版本的,因为整个项目内容还是比较多的,所以分三个部分来讲述,第一个部分先介绍布局,绘制,第二部分介绍点击,滑动切换,长按编辑,拖动排序等,最后一部分介绍添加删除,工具菜单实现,添加打开关闭动画等细节补充。(由于项目代码比较多,都贴出来会很长,所以我们在文章中只贴核心代码部分,后面我会把整个项目的下载地址发出来,有需要的可以直接下载源码查看)。
首先我们看到这样的需求,先考虑如何实现,很明显这次的需求包含了自定义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中了,不出意外就可以看到效果了。那今天就到这里,下一篇我们介绍如何让菜单动起来。