您的位置:首页 > 其它

QQ侧滑面板特效的实现

2016-08-19 13:15 113 查看

ViewDragHelper的介绍

  要实现和QQ5.0侧滑的特效,需要借助谷歌在2013年I/O大会上发布的ViewDragHelper类,提供这个类目的就是为了解决拖拽滑动问题。

  使用v4包中的ViewDragHelper为了兼容低版本,所以在创建ViewDragHelper对象时如果找不到ViewDragHelper这个类,可以从sdk中拷贝出最新的v4包覆盖lib目录中的V4包即可。

效果图:



/**
* Created by Alan on 2016/8/18.
* QQ5.0侧滑菜单特效实现
*/

//自定义一个类继承FrameLayout
public class DragLayout extends FrameLayout {

/**
* ViewDragHelper:
* v4包中的api,2013年谷歌I/O大会上提出,用于解决控件拖动问题。
*/
private ViewDragHelper mDragHelper;
private View mMenuView;//菜单
private View mMainView;//主界面
private int mWidth;//控件宽度
private int mHeight;///控件高度
private int mMRange;//侧拉菜单打开的最大宽度

public DragLayout(Context context) {
super(context);
init();
}

public DragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

//4.处理Callback接口中的回调方法
ViewDragHelper.Callback mCallack = new ViewDragHelper.Callback() {
/**
* 根据返回值决定子控件是否可以拖动, true表示可以拖动
* @param child 被捕获的子控件
* @param pointerId
* @return
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {

return true;
}

/**当开始拖动时回调*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
System.out.println("onViewCaptured -- 拖动");
}

/**
* 根据返回值决定子控件将要显示的位置
* @param child
* @param left
* @param dx
* @return
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mMainView) {

left = fixDragLeft(left);
}
return left;
}

/**
* 获取水平方向拖动的最大范围
* 注意:此方法半不会真正限制子控件的滑动范围,
* 如果要实现拖动效果,应该返回大于0的值
* @param child
* @return mMRange 范围
*/
@Override
public int getViewHorizontalDragRange(View child) {
return mMRange;
}

/**
* 当拖动结束松开手时回调, 需要在此方法中设置菜单为打开或关闭状态
* 侧滑菜单何时打开和关闭?
* 打开的情况:
*   1) 滑动速度大于0
*   2)  速度是0且位置大于拖拽范围的一半
* 关闭的情况:其他的情况都是关闭
*
* @param releasedChild
* @param xvel 松开手时水平方向的速度,像素/秒,往右为正
* @param yvel
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
// 松开手时打开或关闭侧滑菜单
if (xvel > 0) {
open();
} else if (xvel == 0 && mMainView.getLeft() > mMRange / 2) {
open();
} else {
close();
}

}

/**
* 当前界面发生改变回调
* 处理逻辑: 关联滑动, 事件监听, 伴随动画
* @param changedView
* @param left
* @param top
* @param dx
* @param dy
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
//  super.onViewPositionChanged(changedView, left, top, dx, dy);
// 当滑动菜单时,同时滑动主界面
if (changedView == mMenuView) {
// 保持菜单界面位置不变
mMenuView.layout(0, 0, mWidth, mHeight);

// 关联滑动,菜单滑动多少,主界面也滑动多少
int newLeft = mMainView.getLeft() + dx;
newLeft = fixDragLeft(newLeft);
mMainView.layout(newLeft, 0, newLeft + mWidth, mHeight);
}
// 监听打开关闭拖动的状态
listenDragState();
// 伴随动画
animateChildren();

}
};
/**
* 伴随动画
*/
private void animateChildren() {
float percent = ((float) mMainView.getLeft()) / mMRange;

//菜单界面
// 平移: [-mWidth / 2, 0]
// 透明度变化: [0.4, 1]
// 缩放: [0.5f, 1]
mMenuView.setTranslationX(evaluate(-mWidth/2,0,percent));
mMenuView.setAlpha(evaluate(0.4f, 1, percent));
mMenuView.setScaleX(evaluate(0.5f, 1f, percent));
mMenuView.setScaleY(evaluate(0.5f, 1f, percent));

// 主界面:  缩放[1, 0.8f]
mMainView.setScaleX(evaluate(1f, 0.8f, percent));
mMainView.setScaleY(evaluate(1f, 0.8f, percent));

Drawable drawable = getBackground();
if (drawable != null) {
int color = (int) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT);
drawable.setColorFilter(color , PorterDuff.Mode.SRC_OVER);
}
}
/**
* 估值器
*/
public float evaluate(float start,float end,float percent){
// 中间值 = 开始值 + (结束值 - 开始值) * 百分比
return start+(end - start)*percent;
}
public Object evaluateColor(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;

int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;

return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}

//------------侧滑菜单事件监听------以下------------

/**
* 事件监听(打开,关闭,拖动)
*/
private void listenDragState() {
int left = mMainView.getLeft();
if(left == 0){
mCurrentStatus = DragStatus.CLOSE;//关闭
}else if(left == mMRange){
mCurrentStatus = DragStatus.OPEN;//打开
}else{
mCurrentStatus = DragStatus.DRAGGING;//拖动
}

//当事件发生时,回调监听器中的方法
if(mOnDragListener != null){
if(mCurrentStatus == DragStatus.OPEN){

//回调监听器中相应的方法

}else if(mCurrentStatus == DragStatus.CLOSE){

mOnDragListener.onClose();

}else{

float v = ((float) mMainView.getLeft()) / mMRange;
mOnDragListener.onDraging(v);
}
}

}

public boolean isOpen() {

return  mCurrentStatus == DragStatus.OPEN;
}

//定义一个枚举
public enum DragStatus{
OPEN,CLOSE,DRAGGING
}

private DragStatus mCurrentStatus = DragStatus.CLOSE;//默认状态

/**
* 提供状态获取
* @return
*/
public DragStatus getCurrentStatus() {
return mCurrentStatus;
}

/**
* 1.自定义的监听器
*/
private OnDragListener mOnDragListener;
public interface OnDragListener {
void onOpen();
void onClose();
void onDraging(float value);

}
/**
* 给外部提供设置监听器的方法
* @param onDragListener
*/
public void setOnDragListener(OnDragListener onDragListener){
mOnDragListener = onDragListener;
}

//--------------事件监听-----以上---------------------------

private int fixDragLeft(int left) {
if (left < 0) {
left = 0;
} else if (left > mMRange) {
left = mMRange;
}
return left;
}

/**
* 关闭菜单
*/
private void close() {
mMenuView.layout(0, 0, mWidth, mHeight);
//  mMainView.layout(0,0,mWidth,mHeight);
//主界面平滑滚动
mDragHelper.smoothSlideViewTo(mMainView, 0, 0);
//刷新 调用顺序Invalidate --> onDraw() --> computeScroll(重写,在此方法里面需要继续刷新)
ViewCompat.postInvalidateOnAnimation(this);
}

/**
* 打开菜单
*/
private void open() {
mMenuView.layout(0, 0, mWidth, mHeight);
//mMainView.layout(mMRange,0,mWidth+mMRange,mHeight);
// 平滑滚动到某一位置: 第一步
mDragHelper.smoothSlideViewTo(mMainView, mMRange, 0);

//刷新 调用顺序Invalidate --> onDraw() --> computeScroll(重写,在此方法里面需要继续刷新)
ViewCompat.postInvalidateOnAnimation(this);
}

@Override
public void computeScroll() {
super.computeScroll();

//第二步
//如果没有滑动到指定位置,需要继续刷新
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}

private void init() {
//1.创建ViewDragHelper对象
float sensitivity = 1.0f;//敏感值,数值越大则越容易滑动菜单
mDragHelper = ViewDragHelper.create(this, sensitivity, mCallack);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

// 2. 让ViewDragHelper决定是否拦截事件
return mDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// 3. 把事件交给ViewDragHelper处理
mDragHelper.processTouchEvent(event);
//按下时需要返回true,否则无法接收到后续的move和up事件
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return true;
}

return super.onTouchEvent(event);
}

/**
* 当填充结束后回调此方法,注意:不能在此方法获取控件的宽高
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 健壮性处理
if (getChildCount() < 2) {
throw new IllegalStateException("DragLayout至少要有两个子控件");
}
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}

/**
* 执行此方法时,已经调用完了onMeasure,所以可以获取控件宽高
*
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//获取控件的宽高
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
//设置主界面只能滑到 %60;
mMRange = (int) (mWidth * 0.6f);
}
}


头像左右晃动的实现

监听当侧滑面板关闭时通过属性动画实现左右晃动的动画。

mDragLayout.setOnDragListener(new OnDragListener() {
@Override
public void open() {
}
@Override
public void onDragging(float percent) {
iv_header.setAlpha(1 - percent); // 拖动时设置透明度
}
@Override
public void close() {
showToast("close");

TranslateAnimation animation = new TranslateAnimation(0, 10, 0, 0);
animation.setDuration(100);
animation.setRepeatCount(4);
iv_header.startAnimation(animation);

}
});


问题解决

处理菜单打开后主界面列表仍可滑动的问题

自定义主界面根布局控件,并重写拦截方法,当侧滑菜单打开时返回true拦截事件:

public class MyLinearLayout extends LinearLayout {

private DragLayout dragLayout;

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

public void setDragLayout(DragLayout dragLayout) {
this.dragLayout = dragLayout;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 如果侧菜单打开,则拦截事件,不让列表点击或者滚动
if (dragLayout.isOpen()) {
return true;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//如果菜单打开,不允许触摸主界面时,菜单列表滑动
if(layout!= null && layout.isOpen()){
return true;
}
return super.onTouchEvent(event);
}
}


完整源码:点击下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  菜单 侧滑 QQ 特效