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