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

上一篇文章我们介绍了布局和绘制,已经可以显示出基本的效果了,如果没看过的可以先看一下这篇文章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,打开和关闭的动画等。

你可能感兴趣的:(android)