ViewDragHelpe实现QQ侧滑菜单
2015-12-05 00:17
295 查看
ViewDragHelpe的简单使用-QQ5.0菜单特效
效果还是很好看的,虽然现在QQ的侧滑已经变样了,但是对于ViewDragHelper入门来说,这是不错的案例!
在主面板任意位置向右滑动 显示左侧菜单栏,主面板也随左菜单的放大而缩小,底部背景由暗变亮,创建此类有三个步骤,接下来一起看看如何用代码实现吧:
首先定义布局文件/** * 左右两个布局都在一个布局文件中 根节点为自定义的DragLayout 并设置背景图片 * 左为相对布局,右为自定义的相对布局(自定义是为了处理与页面子控件滑动冲突) * Created by shang on 2015/12/3. */ //左菜单布局 <RelativeLayout android:id="@+id/ll_left" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="50dp" android:paddingLeft="10dp" android:paddingRight="50dp" android:paddingTop="50dp"> <ImageView android:id="@+id/img1" android:layout_width="50dp" android:layout_height="50dp" android:clickable="true" android:src="@mipmap/qq"></ImageView> <TextView android:id="@+id/qqName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:layout_toRightOf="@id/img1" android:text="用户名" android:textColor="#ffffff" android:textSize="20sp" /> <TextView android:id="@+id/tv_explain" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/qqName" android:layout_marginLeft="15dp" android:layout_marginTop="5dp" android:layout_toRightOf="@id/img1" android:text="“个性签名.......”" android:textColor="#ffffff" android:textSize="10sp" /> </RelativeLayout> /** * 自定义主面板布局(右) 主面板XML略。 * Created by shang on 2015/12/3. */ public class MyRelativeLayout extends RelativeLayout { private DragLayout draglayout; public MyRelativeLayout(Context context) { super(context); } public MyRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } //重写下面两个触摸事件是为了防止在划出菜单是还可以点击主面板的子控件,因此,父控件拦截触摸事件并消耗掉 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (draglayout != null && draglayout.getStatus() != DragLayout.Status.Close) { return true; } else { return super.onInterceptTouchEvent(ev); } } @Override public boolean onTouchEvent(MotionEvent event) { if (draglayout != null && draglayout.getStatus() != DragLayout.Status.Close) { if (event.getAction() == MotionEvent.ACTION_UP) { draglayout.close(); } return true; } else { return super.onTouchEvent(event); } } public void setDraglayout(DragLayout draglayout) { this.draglayout = draglayout; } }
重点来了!认真阅读方法及参数的注释!
/** * ------ViewDragHelper的使用------ * 自定义侧滑菜单 * Created by shang on 2015/12/3. */ public class DragLayout extends FrameLayout { private ViewDragHelper mHelper; private ViewGroup mLeftContent; private ViewGroup mMainContent; private int mHeight; private int mWidth; private int mRange; //定义面板操作的状态 public static enum Status { Close, Open, Draging } private Status status = Status.Close; private OnDragChangeListener mOnDragChangeListener; /** *定义回调函数,供外部实时调用 */ public interface OnDragChangeListener { public void onOpen(); public void onDraging(float percent); public void onClose(); } public void setOnDragChangeListener(OnDragChangeListener mOnDragChangeListener) { this.mOnDragChangeListener = mOnDragChangeListener; } public DragLayout(Context context) { this(context, null); } public DragLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); /* * 第一步:在构造函数中创建 ViewDragHelper 辅助类 */ mHelper = ViewDragHelper.create(this, 0.1f, callback); } /** * 第二步:转交触摸事件给ViewDragHelper处理 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { try { mHelper.processTouchEvent(event); } catch (Exception e) { e.printStackTrace(); } return true; } /** * 第三步:创建回调函数,用于初始化辅助类,想实现QQ的效果,必须重写其中的五个方法! */ ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { /** * 1.返回值,决定了child是否可以被拖拽 * @param child 被滑动的子View * @param pointerId 多点触摸的手指Id * @return true:可以被拖拽 */ @Override public boolean tryCaptureView(View child, int pointerId) { return true; } /** * 2.返回拖拽的范围,返回一个>0的值,决定了动画的执行时长,水平方向是否可以被滑动 * @param child 子控件 * @return mRange 范围 */ @Override public int getViewHorizontalDragRange(View child) { return mRange; } /** * 3.修正子View水平方向的位置,此时还没有发生真正的移动 * @param child 被拖拽的子View * @param left 建议移动到的位置 * @param dx 与旧值的偏移量 * @return left 移动到的位置 */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { if (child == mMainContent) { left = fixLeft(left); } return left; } /** * 4.控件位置变化时调用 :伴随动画、状态更新、事件回调 * @param changedView 发生改变的子View * @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); //如果移动的是左面板,就设置回原位,并将偏移量作用在主面板(右) .layout(l,t,r,b); if (changedView == mLeftContent) { mLeftContent.layout(0, 0, mWidth, mHeight); int newLeft = mMainContent.getLeft() + dx; //修正左边值 newLeft = fixLeft(newLeft); //主面板的宽度应为newLeft + mWidth mMainContent.layout(newLeft, 0, newLeft + mWidth, mHeight); } /** 添加伴随动画 **/ dispatchDragEvent(); //兼容低版本,手动重绘 invalidate(); } /** * 5.松手之后调用的方法:结束动画 * @param releasedChild 被释放的子View * @param xvel X轴速度 * @param yvel Y轴速度 */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); //打开、关闭主(右)面板 if (xvel >= 0 && mMainContent.getLeft() > mRange * 0.2f) { open(); } else { close(); } } }; /** * 填充View完毕时调用的方法 * 用于检查错误和得到子View对象 */ @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() < 2) { throw new IllegalStateException("错误!至少有两个子控件!"); } if (!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) { throw new IllegalArgumentException("子控件类型错误!"); } mLeftContent = (ViewGroup) getChildAt(0); mMainContent = (ViewGroup) 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(); //计算拖拽的范围 mRange = (int) (mWidth * 0.6f); } private void dispatchDragEvent() { //得到偏移的百分比 float percent = mMainContent.getLeft() * 1.0f / mRange; if (mOnDragChangeListener != null) { mOnDragChangeListener.onDraging(percent); } //更新状态 Status lastStatus = status; status = updateStatus(percent); if (lastStatus != status && mOnDragChangeListener != null) { if (status == Status.Close) { mOnDragChangeListener.onClose(); } else if (status == Status.Open) { mOnDragChangeListener.onOpen(); } } // 执行动画 animViews(percent); } private Status updateStatus(float percent) { if (percent == 0) { return Status.Close; } else if (percent == 1) { return Status.Open; } return Status.Draging; } /** * 伴随动画 渐变、缩放、平移 */ private void animViews(float percent) { //左面板:缩放 percent * 0.5 + 0.5 0.5-->1.0 // mLeftContent.setScaleX(percent * 0.5f + 0.5f); // mLeftContent.setScaleY(percent * 0.5f + 0.5f); /** 兼容低版本 **/ ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f)); ViewHelper.setScaleY(mLeftContent, evaluate(percent, 0.5f, 1.0f)); //左面板:平移 -mWidth / 2.0f -->0 ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0)); //左面板:渐变 0.2f --> 1.0f ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.2f, 1.0f)); //主面板:缩放 1.0f - 0.8f ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f)); ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f)); //背景:亮度变化 getBackground().setColorFilter((Integer) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER); } /** * ArgbEvaluator 中复用方法 --颜色过渡 * * @param fraction * @param startValue * @param endValue * @return */ 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) b5b2 | (int) ((startG + (int) (fraction * (endG - startG))) << 8) | (int) ((startB + (int) (fraction * (endB - startB)))); } /** * 复用类型估值器的方法 得到平移的数值 * * @param fraction * @param startValue * @param endValue * @return */ public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); } public void open() { open(true); } private void open(boolean isSmooth) { int finalLeft = mRange; if (isSmooth) { if (mHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)) { //重绘界面 //invalidate();//此方法会漏帧!不建议使用 //建议:重绘子View所在的容器 ViewCompat.postInvalidateOnAnimation(this); } } else { mMainContent.layout(finalLeft, 0, finalLeft + mWidth, mHeight); } this.status = Status.Open; } public void close() { close(true); } private void close(boolean isSmooth) { int finalLeft = 0; if (isSmooth) { if (mHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)) { //重绘界面 //invalidate();//此方法会漏帧!不建议使用 //建议:重绘子View所在的容器 ViewCompat.postInvalidateOnAnimation(this); } } else { mMainContent.layout(finalLeft, 0, finalLeft + mWidth, mHeight); } this.status = Status.Close; } /** * 每次绘画之前调用 */ @Override public void computeScroll() { super.computeScroll(); //判断是否需要画下一帧动画,传递true if (mHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } private int fixLeft(int left) { if (left < 0) { return 0; } else if (left > mRange) { return mRange; } return left; } public Status getStatus() { return status; } public void setStatus(Status status) { this.status = status; } }
最后!在外部(例如:MainActivity)中就可以通过回调函数控制了
//外部调用 dl = (DragLayout) findViewById(R.id.dl); dl.setOnDragChangeListener(new DragLayout.OnDragChangeListener() { @Override public void onOpen() { } @Override public void onDraging(float percent) { ViewHelper.setAlpha(toimg1, 1 - percent); } @Override public void onClose() { ObjectAnimator oa = ObjectAnimator.ofFloat(toimg1, "translationX", 15f); oa.setInterpolator(new CycleInterpolator(4)); oa.setDuration(500); oa.start(); } });
以上就是全部的代码,直接复用就OK,是不是很简单呢~
相关文章推荐
- 仿QQ5.0侧滑菜单实现
- Android自定义控件--- 手势检测ViewDragHelper
- Android ViewDragHelper 使用详解
- 使用ViewDragHelper实现可移动可点击控件
- [置顶] Google Development
- HDU 2553(N皇后)(DFS)
- java 图形用户界面的设计与实现practice
- java通过网络获取从下位机获得的字节流进行包头判断遇到的问题
- Native Hibernate与Hibernate JPA
- Native Hibernate与Hibernate JPA
- python核心编程-可变参数2
- Native Hibernate与Hibernate JPA
- 第二次冲刺阶段后五天总结
- 界面没有数据的情况下,添加默认图。
- Android自学之路,RecycleView的使用,添加与删除
- Android中static有关的内存泄漏
- 排除Maven传递依赖
- c语言:编写一个程序,可以直接接收键盘字符
- python核心编程-可变参数
- hdoj GCC 3123 (大数阶乘取余&转换)