上一篇文章我们介绍了布局和绘制,已经可以显示出基本的效果了,如果没看过的可以先看一下这篇文章Android 自定义view之扇形菜单(上),这次我们来添加各种点击,滑动和拖动排序等功能,先回忆一下需求:
仔细看上图,我们发现大概的事件有几个:1点击三个tab可以滚动切换页面;2滑动可以随手指滚动,超过30度或者速度达到一定值切换到下一项,到达最后一个后再滚动指示器要从另一边出来(注意这里跟点击时候不一样);3 在快捷开关和常用应用中长按可以进入编辑模式,编辑模式可以删除一项同时剩下的自动排序,拖动交换位置,点击其他区域退出编辑模式。大概功能就这些,那我们就来一个一个的实现。
一、点击和滑动切换
点击和滑动切换其实是放在一起的,无非就是触发方式不一样,点击以后也是要执行一个动画,滑动的时候就是多加了一些判断,最后决定是回滚到原来位置还是切换下一项。回忆上一篇文章,其实我们每一个view在绘制的时候都有考虑滚动的情况,举个例子
FanMenuView.java中的drawchild:
@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;
}
public float getCurrentDegreeByChildIndex(int index) {
int temp = mCurrentPage - index - 1;
if (temp == -2) {
temp = 1;
} else if (temp == 2) {
temp = -1;
}
return temp * CUR_DEGREE;
}
从代码可以看出来,我们在canvas.rotate的时候旋转角度是通过当前child的位置和offset来决定的,那默认我们的offset是0,如果我们实时增加这个offset值,那么就已经可以做到旋转了,同样的在FanSelectTextLayout中也是如此,我们只要在每个类中增加一个方法,改变这个offset值,然后调用invalidate()函数进行重绘那么就可以做到旋转了,ok那么我们就来添加这个方法。
FanMenuView
/**
* 旋转指示器指示的位置
*
* @param cur 当前指示的是哪个位置,(0,1,2)
* @param offset 偏移(-1 -0 - 1)
*/
public void setRotateView(int cur, float offset) {
mCurrentPage = cur;
mOffsetDegree = offset * CUR_DEGREE;
mFavorite.setDisableTouchEvent(true);
mRecently.setDisableTouchEvent(true);
mToolBox.setDisableTouchEvent(true);
switch (mCurrentPage) {
case CardState.CARD_STATE_FAVORITE:
mFavorite.setDisableTouchEvent(false);
break;
case CardState.CARD_STATE_RECENTLY:
mRecently.setDisableTouchEvent(false);
break;
case CardState.CARD_STATE_TOOLBOX:
mToolBox.setDisableTouchEvent(false);
break;
default:
break;
}
invalidate();
}
这里我们添加了一个方法,两个参数,一个是当前选中的哪个项,另一个就是上面说的偏移值,大小从-1到0,0-1的Float值,这样我们在FanRootView中调用这个函数就可以实时改变我们的菜单view的旋转角度了,同理我们添加FanSelectTextLayout和FanSelectTextIndicator,
FanSelectTextLayout
/**
* 旋转指示器指示的位置
* @param cur 当前指示的是哪个位置,(0,1,2)
* @param offset 偏移(-1 -0 - 1)
*/
public void setRotateView(int cur, float offset){
switch (cur){
case CardState.CARD_STATE_FAVORITE:
if(offset == 0)
{
mFavorite.setTextColor(mSelectColor);
mToolBox.setTextColor(mNormalColor);
mRecently.setTextColor(mNormalColor);
}
else
{
int color1 = mColorShades.setShade(Math.abs(offset))
.setFromColor(mSelectColor)
.setToColor(mNormalColor).generate();
mFavorite.setTextColor(color1);
int color2 = mColorShades.setShade(Math.abs(offset))
.setFromColor(mNormalColor)
.setToColor(mSelectColor).generate();
if(offset > 0)
{
mToolBox.setTextColor(color2);
mRecently.setTextColor(mNormalColor);
}
else {
mRecently.setTextColor(color2);
mToolBox.setTextColor(mNormalColor);
}
}
break;
case CardState.CARD_STATE_RECENTLY:
if(offset == 0)
{
mRecently.setTextColor(mSelectColor);
mToolBox.setTextColor(mNormalColor);
mFavorite.setTextColor(mNormalColor);
}
else
{
int color1 = mColorShades.setShade(Math.abs(offset))
.setFromColor(mSelectColor)
.setToColor(mNormalColor).generate();
mRecently.setTextColor(color1);
int color2 = mColorShades.setShade(Math.abs(offset))
.setFromColor(mNormalColor)
.setToColor(mSelectColor).generate();
if(offset > 0)
{
mFavorite.setTextColor(color2);
mToolBox.setTextColor(mNormalColor);
}
else {
mToolBox.setTextColor(color2);
mFavorite.setTextColor(mNormalColor);
}
}
break;
case CardState.CARD_STATE_TOOLBOX:
if(offset == 0)
{
mToolBox.setTextColor(mSelectColor);
mFavorite.setTextColor(mNormalColor);
mRecently.setTextColor(mNormalColor);
}
else
{
int color1 = mColorShades.setShade(Math.abs(offset))
.setFromColor(mSelectColor)
.setToColor(mNormalColor).generate();
mToolBox.setTextColor(color1);
int color2 = mColorShades.setShade(Math.abs(offset))
.setFromColor(mNormalColor)
.setToColor(mSelectColor).generate();
if(offset > 0)
{
mRecently.setTextColor(color2);
mFavorite.setTextColor(mNormalColor);
}
else {
mFavorite.setTextColor(color2);
mRecently.setTextColor(mNormalColor);
}
}
break;
default:
break;
}
}
这里我们直接改变textview的color,color是通过一个color生成器来生成的颜色,这样就根据offset渐变成了我们选中的颜色,color生成器代码如下:
package com.jeden.fanmenu.util;
import android.graphics.Color;
/**
* Created by jeden on 2017/3/15.
*/
public class ColorShades {
private int mFromColor;
private int mToColor;
private float mShade;
public ColorShades setToColor(int toColor)
{
this.mToColor = toColor;
return this;
}
public ColorShades setFromColor(int fromColor)
{
this.mFromColor = fromColor;
return this;
}
public ColorShades setShade(float shade)
{
this.mShade = shade;
return this;
}
public int generate()
{
int fromR = Color.red(mFromColor);
int fromG = Color.green(mFromColor);
int fromB = Color.blue(mFromColor);
int toR = Color.red(mToColor);
int toG = Color.green(mToColor);
int toB = Color.blue(mToColor);
int diffR = toR - fromR;
int diffG = toG - fromG;
int diffB = toB - fromB;
int red = fromR + (int)((diffR * mShade));
int green = fromG + (int)((diffG * mShade));
int blue = fromB + (int)((diffB * mShade));
return Color.rgb(red, green, blue);
}
public String generateString()
{
return String.format("#%06X", 0xFFFFFF & generate());
}
}
FanSelectTextIndicator
/**
* 旋转指示器指示的位置
*
* @param cur 当前指示的是哪个位置,(1,2,3)
* @param offset 偏移(-1 -0 - 1)
*/
public void setRotateView(int cur, float offset) {
int tempCur;
switch (cur) {
case CardState.CARD_STATE_FAVORITE:
if (offset < -0.5) {
tempCur = CardState.CARD_STATE_RECENTLY;
offset += 1;
break;
}
tempCur = cur;
break;
case CardState.CARD_STATE_RECENTLY:
if (offset > 0.5) {
tempCur = CardState.CARD_STATE_FAVORITE;
offset -= 1;
break;
}
tempCur = cur;
break;
case CardState.CARD_STATE_TOOLBOX:
tempCur = cur;
break;
default:
tempCur = cur;
break;
}
tempCur--;
float offsetDegree = (tempCur + offset) * mFanDegree;
mStartDegree = isLeft() ? mFanDegree - offsetDegree : -mFanDegree + offsetDegree;
invalidate();
}
指示器我们一样也是改变了mStartDegree的角度,然后invalidate以后重新onDraw,就可以改变选择器的位置了。
到这里我们旋转的准备工作已经完成了,下面开始具体实现,首先是加点击事件,这里我们只需要在FanSelectTextLayout中添加点击事件,然后回调到FanRootView中即可
FanSelectTextLayout
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mInScreenX = event.getX();
mInScreenY = event.getY();
mLastTime = System.currentTimeMillis();
if(isInTheView(event.getX(), event.getY()))
{
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
float upX = event.getX();
float upY = event.getY();
long curTime = System.currentTimeMillis();
if(FanMenuViewTools.getTwoPointDistance(upX, upY, mInScreenX, mInScreenY) < mTouchSlop && curTime - mLastTime < 400)
{
selectTextAndRefresh();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
public void selectTextAndRefresh()
{
int index;
double degree;
if(isLeft())
{
degree = FanMenuViewTools.getDegreeByPoint(mInScreenX, mHeight - mInScreenY);
}
else
{
degree = FanMenuViewTools.getDegreeByPoint(mWidth - mInScreenX, mHeight - mInScreenY);
}
if(degree < 30)
{
index = CardState.CARD_STATE_RECENTLY;
}
else if(degree > 30 && degree < 60)
{
index = CardState.CARD_STATE_TOOLBOX;
}
else
{
index = CardState.CARD_STATE_FAVORITE;
}
if(mStateChangeable != null)
{
mStateChangeable.selectCardChange(index);
}
}
public boolean isInTheView(float x, float y)
{
float distance = FanMenuViewTools.getTwoPointDistance(x, y, (isLeft() ? 0 : getWidth()), getHeight());
return distance <= mRadius && distance >= mTvPadding;
}
这里我们重写了FanSelectTextLayout的onTouchEvent事件,然后在ACTION_DOWN中判断是否是在我们的view中(这里因为我们的view是叠加放置的,FanSelectTextLayout右下角是有一个centerView的,所以需要判断是否点击的是我们的view),如果是的话就禁止父组件(FanRootView)拦截,然后最后是把结果通过mStateChangeable回调到FanRootView中了,因为我们的整个界面是有很多个view和viewgroup叠加组成的,所以关于滑动和点击事件需要对Android的事件分发机制有比较深的了解,关于这方面的文章挺多的,建议大家可以看一下,对日常处理类似事件非常有帮助,这里其实可以写到FanRootView中,之所以这么写还是想分开,减轻逻辑发复杂度,因为后面很多事件都要在FanRootView中处理。好了废话不多说,我们继续,这里我们把点击事件回调给了FanRootView,那么来看一下FanRootView怎么处理的
FanRootView
package com.jeden.fanmenu.view;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import com.jeden.fanmenu.FanMenuSDK;
import com.jeden.fanmenu.R;
import com.jeden.fanmenu.bean.AppInfo;
import com.jeden.fanmenu.util.FanMenuViewTools;
import com.jeden.fanmenu.common.model.FanMenuConfig;
import com.jeden.fanmenu.common.tools.SwipeTools;
import com.jeden.fanmenu.common.tools.ToolboxHelper;
import com.jeden.fanmenu.util.FanLog;
import com.jeden.fanmenu.view.base.CardState;
import com.jeden.fanmenu.view.base.FanMenuManager;
import com.jeden.fanmenu.view.base.MenuLayoutStateChangeable;
import com.jeden.fanmenu.view.base.PositionState;
/**
* Created by jeden on 2017/3/14.
*/
public class FanRootView extends FrameLayout {
private static final String TAG = FanRootView.class.getSimpleName();
private static final double MIN_SLID_DEGREE = 20;
private static final int MIN_SLID_SPEED = 3000;
private int mPositionState = -1;
private int mSelectTextIndex = -1;
private FanBackgroundView mBackgroundView;
private FanCenterView mCenterView;
private FanSelectTextLayout mSelectTextView;
private FanMenuView mMenuView;
private FanSelectTextIndicator mTextIndicator;
private FanMenuBannerView mBannerView;
/**
* 容器的宽高
*/
private int mWidth;
private int mHeight;
private int mSlideType = SLID_OVER;
private static final int SLID_OVER = 0;
private static final int SLIDING = 1;
private int mEditState = STATE_NORMAL;
private static final int STATE_NORMAL = 0;
private static final int STATE_EDIT = 1;
private FanMenuLayout mEditView;
private float mDownInScreenX;
private float mDownInScreenY;
private int mTouchSlop;
private long mLastTime;
private boolean mClosing = false;
private double mDownAngle;
private VelocityTracker mVelocityTracker;
private int mMaximumVelocity;
private FanMenuItemView mMirrorView;
private FanMenuSDK.FanMenuAdLayoutListener mAdLayoutListener;
public FanRootView(Context context) {
super(context);
initView(context);
}
public FanRootView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public FanRootView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
}
public void initView(Context context) {
ViewConfiguration mConfig = ViewConfiguration.get(context);
mTouchSlop = mConfig.getScaledTouchSlop();
mMaximumVelocity = mConfig.getScaledMaximumFlingVelocity();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mBackgroundView = (FanBackgroundView) findViewById(R.id.fanmenu_background_view_id);
mCenterView = (FanCenterView) findViewById(R.id.fanmenu_center_view_id);
mSelectTextView = (FanSelectTextLayout) findViewById(R.id.fanmenu_select_text_layout_id);
mMenuView = (FanMenuView) findViewById(R.id.fanmenu_menu_view_id);
mTextIndicator = (FanSelectTextIndicator) findViewById(R.id.fanmenu_select_text_indicator_id);
mBannerView = (FanMenuBannerView) findViewById(R.id.fanmenu_ad_view_id);
mSelectTextView.setMenuStateChangeListener(menuStateChangeListener);
mMenuView.setMenuStateChangeListener(menuStateChangeListener);
int halfWidth = getContext().getResources().getDimensionPixelSize(R.dimen.fan_menu_item_half_width);
// mMirrorView = (FanMenuItemView) LayoutInflater.from(getContext()).inflate(R.layout.fan_menu_item_layout, null);
mMirrorView = new FanMenuItemView(getContext());
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(halfWidth * 2, halfWidth * 2);
addView(mMirrorView, lp);
mMirrorView.setVisibility(GONE);
}
public double pointAngle(float x, float y) {
if (mPositionState == PositionState.POSITION_STATE_RIGHT) {
x = mWidth - x;
}
y = mHeight - y;
return FanMenuViewTools.getDegreeByPoint(x, y);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mClosing) {
return true;
}
initVeloCityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownInScreenX = event.getX();
mDownInScreenY = event.getY();
mLastTime = System.currentTimeMillis();
mDownAngle = pointAngle(mDownInScreenX, mDownInScreenY);
break;
case MotionEvent.ACTION_MOVE:
float newX = event.getX();
float newY = event.getY();
if (newY >= mHeight) {
newY = mHeight;
}
if (mEditState == STATE_EDIT) {
break;
}
if ((Math.abs(newX - mDownInScreenX) > mTouchSlop
|| Math.abs(newY - mDownInScreenY) > mTouchSlop)) {
mSlideType = SLIDING;
}
if (mSlideType == SLIDING) {
slidCardAndMove(newX, newY);
}
break;
case MotionEvent.ACTION_UP:
float upX = event.getX();
float upY = event.getY();
if (upY >= mHeight) {
upY = mHeight;
}
long upTime = System.currentTimeMillis();
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
float vx = mVelocityTracker.getXVelocity();
float vy = mVelocityTracker.getYVelocity();
recyleVelocityTracker();
if (mSlideType == SLIDING) {
checkAndMoveToPosition(upX, upY, vx, vy);
break;
}
if (!isPointInMenuView(upX, upY)) {
if (mSlideType == SLID_OVER && upTime - mLastTime < 400 && (Math.abs(upX - mDownInScreenX) < mTouchSlop
&& Math.abs(upY - mDownInScreenY) < mTouchSlop)) {
if (mEditState == STATE_EDIT) {
mEditState = STATE_NORMAL;
mEditView.cancelEditModel();
break;
}
removeFanMenuView();
}
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownInScreenX = ev.getX();
mDownInScreenY = ev.getY();
mLastTime = System.currentTimeMillis();
mDownAngle = pointAngle(mDownInScreenX, mDownInScreenY);
if (mEditState == STATE_EDIT && !isPointInMenuView(mDownInScreenX, mDownInScreenY)) {
return true;
}
return false;
case MotionEvent.ACTION_MOVE:
if (mEditState == STATE_EDIT) {
return false;
}
if (Math.abs(mDownInScreenX - ev.getX()) > mTouchSlop
|| Math.abs(mDownInScreenY - ev.getY()) > mTouchSlop) {
return true;
}
break;
case MotionEvent.ACTION_UP:
if (mEditState == STATE_EDIT && !isPointInMenuView(mDownInScreenX, mDownInScreenY)) {
return true;
}
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
public void updateFanMenuSelectIndex(int selectState, float offset) {
if (selectState > CardState.CARD_STATE_RECENTLY) {
selectState = CardState.CARD_STATE_FAVORITE;
} else if (selectState < CardState.CARD_STATE_FAVORITE) {
selectState = CardState.CARD_STATE_RECENTLY;
}
if (mSelectTextIndex != selectState) {
mSelectTextIndex = selectState;
FanMenuConfig.getMenuConfig().setCardIndex(mSelectTextIndex);
FanMenuConfig.saveMenuConfig();
}
mSelectTextView.setRotateView(this.mSelectTextIndex, offset);
mTextIndicator.setRotateView(this.mSelectTextIndex, offset);
mMenuView.setRotateView(this.mSelectTextIndex, offset);
}
public void removeFanMenuView() {
closeFanMenu();
}
MenuLayoutStateChangeable menuStateChangeListener = new MenuLayoutStateChangeable() {
@Override
public void selectCardChange(int selectState) {
if (mEditState == STATE_EDIT) {
return;
}
scrollToState(mSelectTextIndex, selectState, 0);
}
@Override
public void longClickStateChange(View view, boolean isEditModel) {
if (mEditView != null) {
mEditView.endEditModel();
}
mEditState = isEditModel ? STATE_EDIT : STATE_NORMAL;
if (view != null && view instanceof FanMenuLayout)
mEditView = (FanMenuLayout) view;
}
@Override
public void dragViewAndRefresh(float x, float y, AppInfo appInfo, boolean hidden, boolean isToolbox) {
if (hidden) {
mMirrorView.setVisibility(GONE);
return;
}
if (mMirrorView.getVisibility() == GONE) {
mMirrorView.setVisibility(View.VISIBLE);
mMirrorView.setToolboxModel(isToolbox);
mMirrorView.setItemIcon(appInfo.getAppIcon());
mMirrorView.setTitle(appInfo.getAppLabel());
mMirrorView.showDelBtn();
}
x += mMenuView.getLeft();
y += mMenuView.getTop();
mMirrorView.setTranslationX(x);
mMirrorView.setTranslationY(y);
}
@Override
public void addBtnClicked(final View view, int selectCard) {
FanMenuManager.showDialog(getContext(), selectCard, new FanMenuDialog.DialogSubmitListener() {
@Override
public void dialogSubmit() {
((FanMenuLayout) view).refreshData();
}
});
}
@Override
public void menuItemClicked(View view, AppInfo appInfo) {
FanLog.v(TAG, "menuItemClicked appInfo:" + appInfo);
if (switchOrStartActivity(view, appInfo)) {
FanMenuManager.closeFanMenu(getContext());
}
}
};
public boolean switchOrStartActivity(View view, AppInfo appInfo) {
if (appInfo == null) {
return false;
}
Intent intent = appInfo.getIntent();
if (intent != null) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
return true;
}
SwipeTools tools = ToolboxHelper.checkSwipeTools(getContext(), appInfo);
if (tools != null) {
return tools.changeStateWithResult(getContext());
}
return false;
}
public void scrollToState(final int fromeState, final int toState, float offset) {
final float temp = toState - fromeState;
ValueAnimator va = ValueAnimator.ofFloat(offset, temp);
va.setDuration((long) Math.round(Math.abs(temp - offset)) * 100 + 250);
va.setInterpolator(new AccelerateDecelerateInterpolator());
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
// if (temp > 1 && value > 0 && (value > 0.98 && value < 1.02)) {
// value = 1;
// } else if (temp < -1 && value < 0 && (value < -0.98 && value > -1.02)) {
// value = -1;
// }
if (value >= 1) {
updateFanMenuSelectIndex(fromeState + 1, value - 1);
} else if (value <= -1) {
updateFanMenuSelectIndex(fromeState - 1, value + 1);
} else {
updateFanMenuSelectIndex(fromeState, value);
}
}
});
va.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
updateFanMenuSelectIndex(toState, 0.0f);
if (mSlideType == SLIDING) {
mSlideType = SLID_OVER;
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
va.start();
}
public boolean isPointInMenuView(float x, float y) {
float upDistance = 0;
if (mPositionState == PositionState.POSITION_STATE_LEFT) {
upDistance = FanMenuViewTools.getTwoPointDistance(x, y, 0, mHeight);
} else if (mPositionState == PositionState.POSITION_STATE_RIGHT) {
upDistance = FanMenuViewTools.getTwoPointDistance(x, y, mWidth, mHeight);
}
int centerViewH = mCenterView.getMeasuredHeight();
int menuViewH = mMenuView.getMeasuredHeight();
if (upDistance < centerViewH || upDistance > menuViewH) {
return false;
}
return true;
}
public void slidCardAndMove(float x, float y) {
double newAngle = pointAngle(x, y);
float offset = (float) ((newAngle - mDownAngle) / 90.0);
updateFanMenuSelectIndex(mSelectTextIndex, offset);
}
public void checkAndMoveToPosition(float x, float y, float vx, float vy) {
double newAngle = pointAngle(x, y);
double temp = newAngle - mDownAngle;
float tempSpeed = mPositionState == PositionState.POSITION_STATE_LEFT ? vy + vx : vy - vx;
if (temp > MIN_SLID_DEGREE || tempSpeed > MIN_SLID_SPEED) {
scrollToState(mSelectTextIndex, mSelectTextIndex + 1, (float) (temp / 90.0));
} else if (temp < -MIN_SLID_DEGREE || tempSpeed < -MIN_SLID_SPEED) {
scrollToState(mSelectTextIndex, mSelectTextIndex - 1, (float) (temp / 90.0));
} else {
scrollToState(mSelectTextIndex, mSelectTextIndex, (float) (temp / 90.0));
}
}
/**
* 初始化VelocityTracker
*
* @param event 滑动时的手势
*/
private void initVeloCityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
/**
* 回收VelocityTracker
*/
private void recyleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
}
ok,这里基本上把FanRootView的代码贴完了,代码比较长,里面有一会儿用到的拖拽排序相关代码,先全贴出来,然后我们一点一点来分析,第一步先看构造函数,在构造函数中我们调用了initview来初始化一些view相关的参数,然后在onFinishInflate中我们初始化view和设置相关监听,其中就包括了上面我们的点击事件的监听,那么在点击以后就会回调到menuStateChangeListener中,在selectCardChange中我们调用了scrollToState方法,该方法有三个参数,第一个是从哪种状态开始,第二个参数是到哪种状态,第三个就是一个偏移值,这里点击事件中我们是从当前状态到指定的状态,然后偏移值是0,然后在scrollToState中我们创建了一个属性动画,让offset平滑的过渡到from-to的差值(这里有可能大于1因为点击切换的时候是可以跳过中间的从第一个切换到第三个,所以里面在大于1的时候我们先把from根据方向+1或者-1,然后offset转换回-1到0或者0-1)在update中我们调用了updateFanMenuSelectIndex方法进行更新,updateFanMenuSelectIndex中我们其实就是调用了FanSelectLayout、FanSelectTextIndicator,FanMenuView这三个view的setRotateView方法,具体上面已经说过了,然后点击事件就处理完了。
下面我们来看滑动事件,上面的处理完以后其实滑动也就比较简单了,我们重写了OnInterceptTouchEvent和OnTouchEvent,大家都知道onInterceptTouchEvent可以选择viewgroup是否要拦截事件分发,我们分别在down的编辑模式时,move的滑动模式,和up的非menuview区域抬起这三个地方返回了true,就是这三种情况我们的FanRootView来接管touch事件,其中编辑模式是一会儿拖拽的时候用到的,我们看move和up的,一个是在滑动的时候我们需要实时更新view的,还有一个是在点击空白区域消失的功能(这里判断只包括了是否在Menuview中,所以结果就包括了center,selecttext,和空白部分,其中Selecttext我们在down的时候就判断了如果是在selecttext中我们不让父组件拦截,所以就只剩下center和空白部分,这里的功能都一样 ,都是点击关闭)。
这里我们先看move的,move在OnTouchEvent中,我们判断滑动距离超过了系统默认的滑动距离就开启滑动模式,并开始滑动,滑动非常简单就是计算了当前坐标的角度,然后用DOWN时候的角度和当前角度做计算,得出offset值,然后直接调用updateFanMenuSelectIndex方法就可以了,然后在UP中如果是滑动模式的UP就checkAndMoveToPosition,其中包括了我们当前的速度,然后判断要不要滑动到下一项,然后还是调用scrollToState来进行滑动。到这里我们的滑动和点击都完成了,可以滑动试试效果了。
二、长按编辑,拖动排序
上面我们完成了滑动和点击,下面我们来实现长安编辑和拖动排序,这里主要功能是在FanMenuLayout中,首先是长按,我们可以在down的时候handler.postDelayed一个任务,然后在任务中触发编辑的逻辑,在move和up中都进行remove操作,这样如果move或者up就不会触发长按的任务,还是来看代码:
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 Administrator 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);
// setDrawingCacheEnabled(true);
setDrawingCacheBackgroundColor(0xFF000000);
}
public void addAllItemView() {
LayoutInflater inflater = LayoutInflater.from(getContext());
FanMenuItemView itemView;
List tempAppInfo = getData();
for (AppInfo info : tempAppInfo) {
// itemView = (FanMenuItemView) inflater.inflate(R.layout.fan_menu_item_layout, null);
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;
}
public void saveChangeData() {
switch (mMenuType) {
case CardState.CARD_STATE_FAVORITE:
ContentProvider.getInstance().saveFavorite();
break;
case CardState.CARD_STATE_RECENTLY:
ContentProvider.getInstance().saveRecently();
break;
case CardState.CARD_STATE_TOOLBOX:
ContentProvider.getInstance().saveToolbox();
break;
default:
break;
}
}
OnTouchListener mMyViewTouchListener = new View.OnTouchListener() {
private float mDownX;
private float mDownY;
private int mDownType = DOWN_NULL;
private static final int DOWN_NULL = -1;
private static final int DOWN_DELETE = 1;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mIsDeleting) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
mLastTime = System.currentTimeMillis();
if (v instanceof FanMenuItemView) {
if (mLongClickable && !mIsEditModel) {
handler.postDelayed(mLongClickRunnable, 600);
}
mDownSelectChild = (FanMenuItemView) v;
if (mIsEditModel && isTouchTheDeleteView(mDownSelectChild, mDownX, mDownY)) {
mDownType = DOWN_DELETE;
}
} else {
mDownSelectChild = null;
}
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
if (Math.abs(moveX - mDownX) > mTouchSlop || Math.abs(moveY - mDownY) > mTouchSlop) {
mDownType = DOWN_NULL;
handler.removeCallbacks(mLongClickRunnable);
}
if (mIsEditModel && mDownSelectChild != null) {
mIsDragging = true;
addMirrorViewAndDrag(moveX - mDownX, moveY - mDownY);
}
break;
case MotionEvent.ACTION_UP:
float upX = event.getX();
float upY = event.getY();
long cur = System.currentTimeMillis();
if (mIsEditModel && mIsDragging) {
restoreDragView(upX - mDownX, upY - mDownY);
break;
}
if (Math.abs(upX - mDownX) > mTouchSlop || Math.abs(upY - mDownY) > mTouchSlop
|| cur - mLastTime < 300) {
if (mDownType == DOWN_DELETE) {
deleteItemAndRefresh(mDownSelectChild);
} else if (!mIsEditModel) {
handler.removeCallbacks(mLongClickRunnable);
btnClicked(v.getTag());
}
}
mDownType = DOWN_NULL;
break;
default:
handler.removeCallbacks(mLongClickRunnable);
mDownType = DOWN_NULL;
break;
}
return true;
}
};
public void btnClicked(Object obj) {
if (obj == null || mStateChangeable == null) {
return;
}
if (obj instanceof AppInfo) {
mStateChangeable.menuItemClicked(this, (AppInfo) obj);
} else {
mStateChangeable.addBtnClicked(this, mMenuType);
}
}
public boolean isTouchTheDeleteView(FanMenuItemView v, float x, float y) {
Rect delete = v.getDeleteRect();
if (x > delete.left && x < delete.right && y > delete.top && y < delete.bottom) {
return true;
}
return false;
}
public void refreshData() {
removeAllViews();
addAllItemView();
requestLayout();
mCache = null;
}
public void deleteItemAndRefresh(FanMenuItemView child) {
List datas = getData();
AppInfo appInfo = (AppInfo) child.getTag();
int index = datas.indexOf(appInfo);
deleteItemAndReorder(child, index);
datas.remove(appInfo);
saveChangeData();
}
public void deleteItemAndReorder(final FanMenuItemView child, final int index) {
mIsDeleting = true;
final int count = getChildCount();
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1.0f, 0f);
valueAnimator.setDuration(150);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float values = (float) animation.getAnimatedValue();
child.setScaleX(values);
child.setScaleY(values);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
ArrayList delFrom = new ArrayList<>();
ArrayList delTo = new ArrayList<>();
int[] p;
for (int i = 0; i < count; i++) {
View temp = getChildAt(i);
if (i != index) {
delFrom.add(temp);
p = new int[2];
p[0] = isLeft() ? 0 : getWidth();
p[1] = getHeight();
getItemCenterPoint(delTo.size(), count - 1, p);
p[0] -= mItemHalfWidth;
p[1] -= mItemHalfWidth;
p[0] -= temp.getX();
p[1] -= temp.getY();
delTo.add(p);
}
}
deleteViewByAnimator(delFrom, delTo, child);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.start();
}
public void deleteViewByAnimator(final ArrayList delFrom, final ArrayList delTo, final FanMenuItemView child) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1.0f);
valueAnimator.setDuration(250);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float values = (float) animation.getAnimatedValue();
for (int i = 0; i < delFrom.size(); i++) {
View temp = delFrom.get(i);
float x = delTo.get(i)[0] * values;
float y = delTo.get(i)[1] * values;
temp.setTranslationX(x);
temp.setTranslationY(y);
}
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
for (View temp : delFrom) {
temp.setTranslationX(0);
temp.setTranslationY(0);
}
FanMenuLayout.this.removeView(child);
if (getChildCount() == 1 && !(getChildAt(0) instanceof FanMenuItemView) && mStateChangeable != null) {
mStateChangeable.longClickStateChange(null, false);
endEditModel();
}
mIsDeleting = false;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.start();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (mDisableTouchEvent) {
return false;
}
return super.dispatchTouchEvent(event);
}
public void changeTwoChildPosition(final FanMenuItemView from, final AppInfo toApp) {
final float fromTranX = from.getTranslationX();
final float fromTranY = from.getTranslationY();
final float offsetX = mLastSlot[1] - (from.getLeft() + fromTranX);
final float offsetY = mLastSlot[2] - (from.getTop() + fromTranY);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1.0f);
valueAnimator.setDuration(250);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float values = (float) animation.getAnimatedValue();
from.setTranslationX(fromTranX + offsetX * values);
from.setTranslationY(fromTranY + offsetY * values);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
List datas = getData();
AppInfo fromApp = (AppInfo) from.getTag();
int fromIndex = datas.indexOf(fromApp);
int toIndex = datas.indexOf(toApp);
datas.set(toIndex, fromApp);
datas.set(fromIndex, toApp);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.start();
}
public void addMirrorViewAndDrag(float x, float y) {
if (mDownSelectChild == null || mStateChangeable == null) {
return;
}
x += mDownSelectChild.getTranslationX();
y += mDownSelectChild.getTranslationY();
if (mDownSelectChild.getVisibility() != GONE) {
AppInfo appInfo = (AppInfo) mDownSelectChild.getTag();
mStateChangeable.dragViewAndRefresh(getLeft() + mDownSelectChild.getLeft() + x,
getTop() + mDownSelectChild.getTop() + y, appInfo, false, mMenuType == CardState.CARD_STATE_TOOLBOX);
mDownSelectChild.setVisibility(GONE);
mLastSlot[0] = indexOfChild(mDownSelectChild);
mLastSlot[1] = mDownSelectChild.getLeft();
mLastSlot[2] = mDownSelectChild.getTop();
int count = getChildCount();
for (int i = 0; i < mLastSlotView.length; i++) {
if (i < count) {
View temp = getChildAt(i);
if (temp instanceof FanMenuItemView)
mLastSlotView[i] = (FanMenuItemView) temp;
else
mLastSlotView[i] = null;
} else {
mLastSlotView[i] = null;
}
}
initDragIndexRect();
} else {
mStateChangeable.dragViewAndRefresh(getLeft() + mDownSelectChild.getLeft() + x,
getTop() + mDownSelectChild.getTop() + y, null, false, mMenuType == CardState.CARD_STATE_TOOLBOX);
}
int selectIndex = getChildByPoint(mDownSelectChild.getLeft() + mItemHalfWidth + (int) x,
mDownSelectChild.getTop() + mItemHalfWidth + (int) y);
if (selectIndex >= 0 && selectIndex < mLastSlotView.length && mLastSlotView[selectIndex] != null) {
if (selectIndex == mLastSlot[0]) {
return;
}
FanLog.v(TAG, "addMirrorView getChildByPoint child:" + ((AppInfo) mLastSlotView[selectIndex].getTag()).getAppLabel());
FanMenuItemView from = mLastSlotView[selectIndex];
changeTwoChildPosition(from, getData().get(mLastSlot[0]));
FanMenuItemView temp = mLastSlotView[mLastSlot[0]];
mLastSlotView[mLastSlot[0]] = from;
mLastSlotView[selectIndex] = temp;
mLastSlot[0] = selectIndex;
mLastSlot[1] = mDragIndexPoint[selectIndex].left;
mLastSlot[2] = mDragIndexPoint[selectIndex].top;
}
}
public void restoreDragView(final float x, final float y) {
if (mStateChangeable == null) {
return;
}
final float offsetX = mLastSlot[1] - (mDownSelectChild.getLeft() + x);
final float offsetY = mLastSlot[2] - (mDownSelectChild.getTop() + y);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1.0f);
valueAnimator.setDuration(250);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float values = (float) animation.getAnimatedValue();
mStateChangeable.dragViewAndRefresh(getLeft() + mDownSelectChild.getLeft() + offsetX * values + x,
getTop() + mDownSelectChild.getTop() + offsetY * values + y, null, false,
mMenuType == CardState.CARD_STATE_TOOLBOX);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mStateChangeable.dragViewAndRefresh(getLeft() + mLastSlot[1], getTop() + mLastSlot[2], null, true,
mMenuType == CardState.CARD_STATE_TOOLBOX);
mDownSelectChild.setVisibility(VISIBLE);
View add = findViewWithTag(ADD_VIEW_TAG);
removeAllViews();
for (int i = 0; i < mLastSlotView.length; i++) {
View temp = mLastSlotView[i];
if (temp != null) {
temp.setTranslationX(0);
temp.setTranslationY(0);
addView(temp);
}
}
addView(add);
requestLayout();
mIsDragging = false;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.start();
}
public void initDragIndexRect() {
int count = getChildCount();
count = count > 9 ? 9 : count;
Rect r;
for (int i = 0; i < count; i++) {
int[] p = new int[2];
p[0] = isLeft() ? 0 : getWidth();
p[1] = getHeight();
getItemCenterPoint(i, count, p);
r = new Rect();
r.left = p[0] - mItemHalfWidth;
r.top = p[1] - mItemHalfWidth;
r.right = p[0] + mItemHalfWidth;
r.bottom = p[1] + mItemHalfWidth;
if (i < mDragIndexPoint.length) {
mDragIndexPoint[i] = r;
}
}
}
public int getChildByPoint(float x, float y) {
for (int i = 0; i < mDragIndexPoint.length; i++) {
Rect r = mDragIndexPoint[i];
if (r != null && isPointInRect(r, x, y)) {
return i;
}
}
return -1;
}
public boolean isPointInRect(Rect r, float x, float y) {
return x > r.left && x < r.right && y > r.top && y < r.bottom;
}
public void cancelEditModel() {
endEditModel();
}
public void startEditModel() {
FanLog.v(TAG, "startEditModel");
mIsEditModel = true;
List data = getData();
for (AppInfo appInfo : data) {
FanMenuItemView child = (FanMenuItemView) this.findViewWithTag(appInfo);
child.showDelBtn();
}
}
public void endEditModel() {
FanLog.v(TAG, "endEditModel");
mIsEditModel = false;
List data = getData();
for (AppInfo appInfo : data) {
FanMenuItemView child = (FanMenuItemView) this.findViewWithTag(appInfo);
child.hideDelBtn();
}
updateCacheDrawable();
}
@Override
public void setPositionState(int state) {
super.setPositionState(state);
mCache = null;
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP &&
mMenuType == CardState.CARD_STATE_RECENTLY) {
ContentProvider.getInstance().updateRecently(getContext());
refreshData();
}
}
public void updateCacheDrawable() {
if (getWidth() <= 0 || getHeight() <= 0) {
return;
}
FanLog.v(TAG, "updateCacheDrawable");
Bitmap cache = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_4444);
Canvas c = new Canvas(cache);
draw(c);
mCache = new BitmapDrawable(cache);
}
public Drawable getCacheDrawable() {
if (mCache == null) {
updateCacheDrawable();
}
return mCache;
}
public void setRotateStart() {
if (mRotating) {
return;
}
Drawable cache = getCacheDrawable();
if (cache == null) {
return;
}
setBackground(cache);
mRotating = true;
setChildVisible(false);
}
public void setRotateEnd() {
if (!mRotating) {
return;
}
setBackgroundColor(mBackgroundColor);
mRotating = false;
setChildVisible(true);
}
private void setChildVisible(boolean visible) {
int count = getChildCount();
int v = visible ? VISIBLE : GONE;
for (int i = 0; i < count; i++) {
getChildAt(i).setVisibility(v);
}
}
private class LongClickRunnable implements Runnable {
@Override
public void run() {
if (mStateChangeable != null && !mDisableTouchEvent) {
mStateChangeable.longClickStateChange(FanMenuLayout.this, true);
startEditModel();
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
long[] pattern = {0, 45};
mVibrator.vibrate(pattern, -1);
}
}
}
}
这里就是FanMenuLayout的全部代码了,也比较长,我们在第一篇中已经分析过了绘制部分,这里我们主要看touch事件,在初始化的时候我们把每一个item项都add到了当前viewgroup中,并添加了Item的OnTouchListener事件,这里我们处理点击和长按和编辑模式的滑动功能(这里我们并没有设置父组件不拦截事件,因为滑动在FanRootView中其实已经处理过了)。我们先分析长按,长按在down中,我们记录了down时候点击的是哪一项,然后放到了mDownSelectChild中,然后post了一个任务,在任务中我们开启了编辑模式,就是把除了addItem的所有child的close按钮都show出来,然后震动一下,通知FanRootView进入编辑模式,我们把mDownSelectChild传给了FanRootView,,因为拖拽的时候我们需要把当前Item隐藏,然后添加一个item上去跟随手指滑动,但是猎豹的是可以拖拽到屏幕任意位置的,所以为了达到效果一致,我们只能在FanRootView中添加这个镜像,然后通过一个函数来分发坐标位置来更新镜像的位置。
进入编辑模式后,我们就可以开始addMirrorViewAndDrag,这里逻辑比较复杂,在刚开始滑动的时候我们通知FanRootView添加镜像,然后更新坐标,再把所有child的Rect计算出来保存到了一个数组中,这样就方便我们实时比较当前触摸的点是否达到了某一个item项,然后就开始触发交换,如果实时的去轮询计算坐标会比较慢,效率不高,然后后续的滑动我们就比较当前点是否在某一个item内,如果在就把当前item和mLastSLotView做交换,具体交换比较简单就是把当前view通过TranslationX Y来移动到指定位置,然后再把数据结构中的两项交换位置,这里注意一点在动画开始的时候就已经做了交换和保存,并不是在动画结束,因为动画结束前有可能已经拖动到另外一个了,这样交换的数据就是乱的,比如A和B要交换,但是动画没结束就立马要进行A和D的交换,如果动画结束才进行真正的交换那么结果就是B和D都到了B所在位置。
最后我们在UP的时候restoreDragView,就是把拖拽的view移动到最后的slotview的位置就ok了,动画结束以后我们把所有view都remove,然后再根据新的顺序add上去,因为提前保存过,所以这个过程并不耗时,到此我们的拖拽排序已经完成了,在up的时候如果不是编辑模式,那么downview就是我们点击的view,然后处理点击事件就可以了,到此我们就把滑动,点击,拖拽排序,编辑模式等完成了,最后一篇我们介绍添加item,删除item,打开和关闭的动画等。