ViewDragHelper实现QQ侧滑效果

前言

侧滑的实现方式有很多方式来实现,这次总结的ViewDragHelper就是其中一种方式,ViewDragHelper是2013年谷歌I/O大会发布的新的控件,为了解决界面控件拖拽问题。下面就是自己学习写的一个实现类似于QQ侧滑效果的实现。
activity_main.xml:

<com.yctc.drag.DragLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/dl" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/bg" tools:context=".MainActivity" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="50dp" android:paddingLeft="10dp" android:paddingRight="50dp" android:paddingTop="50dp" > <ImageView android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/head" /> <ListView android:id="@+id/lv_left" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView> </LinearLayout> <com.yctc.drag.MyLinearLayout android:id="@+id/mll" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#18B6EF" android:gravity="center_vertical" > <ImageView android:id="@+id/iv_header" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginLeft="15dp" android:src="@drawable/head" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="Header" /> </RelativeLayout> <ListView android:id="@+id/lv_main" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView> </com.yctc.drag.MyLinearLayout> </com.yctc.drag.DragLayout>

DragLayout.Java:

public class DragLayout extends FrameLayout { private static final String TAG = "TAG"; private ViewDragHelper mDragHelper; private ViewGroup mLeftContent; private ViewGroup mMainContent; private OnDragStatusChangeListener mListener; private Status mStatus = Status.Close; /** * 状态枚举 */ public static enum Status { Close, Open, Draging; } public interface OnDragStatusChangeListener{ void onClose(); void onOpen(); void onDraging(float percent); } public Status getStatus() { return mStatus; } public void setStatus(Status mStatus) { this.mStatus = mStatus; } public void setDragStatusListener(OnDragStatusChangeListener mListener){ this.mListener = mListener; } public DragLayout(Context context) { this(context, null); } public DragLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // a.初始化 (通过静态方法) mDragHelper = ViewDragHelper.create(this , mCallback); } ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() { // c. 重写事件 // 1. 根据返回结果决定当前child是否可以拖拽 // child 当前被拖拽的View // pointerId 区分多点触摸的id @Override public boolean tryCaptureView(View child, int pointerId) { Log.d(TAG, "tryCaptureView: " + child); return true; }; @Override public void onViewCaptured(View capturedChild, int activePointerId) { Log.d(TAG, "onViewCaptured: " + capturedChild); // 当capturedChild被捕获时,调用. super.onViewCaptured(capturedChild, activePointerId); } @Override public int getViewHorizontalDragRange(View child) { // 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度 return mRange; } // 2. 根据建议值 修正将要移动到的(横向)位置 (重要) // 此时没有发生真正的移动 public int clampViewPositionHorizontal(View child, int left, int dx) { // child: 当前拖拽的View // left 新的位置的建议值, dx 位置变化量 // left = oldLeft + dx; Log.d(TAG, "clampViewPositionHorizontal: " + "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " +left); if(child == mMainContent){ left = fixLeft(left); } return left; } // 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面) // 此时,View已经发生了位置的改变 @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { // changedView 改变位置的View // left 新的左边值 // dx 水平方向变化量 super.onViewPositionChanged(changedView, left, top, dx, dy); Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx); int newLeft = left; if(changedView == mLeftContent){ // 把当前变化量传递给mMainContent newLeft = mMainContent.getLeft() + dx; } // 进行修正 newLeft = fixLeft(newLeft); if(changedView == mLeftContent) { // 当左面板移动之后, 再强制放回去. mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight); mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight); } // 更新状态,执行动画 dispatchDragEvent(newLeft); // 为了兼容低版本, 每次修改值之后, 进行重绘 invalidate(); } // 4. 当View被释放的时候, 处理的事情(执行动画) @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { // View releasedChild 被释放的子View // float xvel 水平方向的速度, 向右为+ // float yvel 竖直方向的速度, 向下为+ Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel); super.onViewReleased(releasedChild, xvel, yvel); // 判断执行 关闭/开启 // 先考虑所有开启的情况,剩下的就都是关闭的情况 if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){ open(); }else if (xvel > 0) { open(); }else { close(); } } @Override public void onViewDragStateChanged(int state) { // TODO Auto-generated method stub super.onViewDragStateChanged(state); } }; /** * 根据范围修正左边值 * @param left * @return */ private int fixLeft(int left) { if(left < 0){ return 0; }else if (left > mRange) { return mRange; } return left; } protected void dispatchDragEvent(int newLeft) { float percent = newLeft * 1.0f/ mRange; //0.0f -> 1.0f Log.d(TAG, "percent: " + percent); if(mListener != null){ mListener.onDraging(percent); } // 更新状态, 执行回调 Status preStatus = mStatus; mStatus = updateStatus(percent); if(mStatus != preStatus){ // 状态发生变化 if(mStatus == Status.Close){ // 当前变为关闭状态 if(mListener != null){ mListener.onClose(); } }else if (mStatus == Status.Open) { if(mListener != null){ mListener.onOpen(); } } } // * 伴随动画: animViews(percent); } private Status updateStatus(float percent) { if(percent == 0f){ return Status.Close; }else if (percent == 1.0f) { return Status.Open; } return Status.Draging; } private void animViews(float percent) { // > 1. 左面板: 缩放动画, 平移动画, 透明度动画 // 缩放动画 0.0 -> 1.0 >>> 0.5f -> 1.0f >>> 0.5f * percent + 0.5f // mLeftContent.setScaleX(0.5f + 0.5f * percent); // mLeftContent.setScaleY(0.5f + 0.5f * percent); ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f)); ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent); // 平移动画: -mWidth / 2.0f -> 0.0f ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0)); // 透明度: 0.5 -> 1.0f ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f)); // > 2. 主面板: 缩放动画 // 1.0f -> 0.8f ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f)); ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f)); // > 3. 背景动画: 亮度变化 (颜色变化) getBackground().setColorFilter((Integer)evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), Mode.SRC_OVER); } /** * 估值器 * @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); } /** * 颜色变化过度 * @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) | (int)((startG + (int)(fraction * (endG - startG))) << 8) | (int)((startB + (int)(fraction * (endB - startB)))); } @Override public void computeScroll() { super.computeScroll(); // 2. 持续平滑动画 (高频率调用) if(mDragHelper.continueSettling(true)){ // 如果返回true, 动画还需要继续执行 ViewCompat.postInvalidateOnAnimation(this); } } public void close(){ close(true); } /** * 关闭 */ public void close(boolean isSmooth) { int finalLeft = 0; if(isSmooth){ // 1. 触发一个平滑动画 if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){ // 返回true代表还没有移动到指定位置, 需要刷新界面. // 参数传this(child所在的ViewGroup) ViewCompat.postInvalidateOnAnimation(this); } }else { mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight); } } public void open(){ open(true); } /** * 开启 */ public void open(boolean isSmooth) { int finalLeft = mRange; if(isSmooth){ // 1. 触发一个平滑动画 if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){ // 返回true代表还没有移动到指定位置, 需要刷新界面. // 参数传this(child所在的ViewGroup) ViewCompat.postInvalidateOnAnimation(this); } }else { mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight); } } private int mHeight; private int mWidth; private int mRange; // b.传递触摸事件 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 传递给mDragHelper return mDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { try { mDragHelper.processTouchEvent(event); } catch (Exception e) { e.printStackTrace(); } // 返回true, 持续接受事件 return true; } @Override protected void onFinishInflate() { super.onFinishInflate(); // Github // 写注释 // 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类) if(getChildCount() < 2){ throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at least."); } if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){ throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an instance of ViewGroup"); } mLeftContent = (ViewGroup) getChildAt(0); mMainContent = (ViewGroup) getChildAt(1); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 当尺寸有变化的时候调用 mHeight = getMeasuredHeight(); mWidth = getMeasuredWidth(); // 移动的范围 mRange = (int) (mWidth * 0.6f); } }

MyLineatLayout.java:

public class MyLinearLayout extends LinearLayout { private DragLayout mDragLayout; public MyLinearLayout(Context context) { super(context); } public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public void setDraglayout(DragLayout mDragLayout){ this.mDragLayout = mDragLayout; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 如果当前是关闭状态, 按之前方法判断 if(mDragLayout.getStatus() == Status.Close){ return super.onInterceptTouchEvent(ev); }else { return true; } } @Override public boolean onTouchEvent(MotionEvent event) { // 如果当前是关闭状态, 按之前方法处理 if(mDragLayout.getStatus() == Status.Close){ return super.onTouchEvent(event); }else { // 手指抬起, 执行关闭操作 if(event.getAction() == MotionEvent.ACTION_UP){ mDragLayout.close(); } return true; } } }

MainActivity.java:

public class MainActivity extends Activity { private static final String TAG = "TAG"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); final ListView mLeftList = (ListView) findViewById(R.id.lv_left); final ListView mMainList = (ListView) findViewById(R.id.lv_main); final ImageView mHeaderImage = (ImageView) findViewById(R.id.iv_header); MyLinearLayout mLinearLayout = (MyLinearLayout) findViewById(R.id.mll); // 查找Draglayout, 设置监听 DragLayout mDragLayout = (DragLayout) findViewById(R.id.dl); // 设置引用 mLinearLayout.setDraglayout(mDragLayout); mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() { @Override public void onOpen() { Utils.showToast(MainActivity.this, "onOpen"); // 左面板ListView随机设置一个条目 Random random = new Random(); int nextInt = random.nextInt(50); mLeftList.smoothScrollToPosition(nextInt); } @Override public void onDraging(float percent) { Log.d(TAG, "onDraging: " + percent);// 0 -> 1 // 更新图标的透明度 // 1.0 -> 0.0 ViewHelper.setAlpha(mHeaderImage, 1 - percent); } @Override public void onClose() { Utils.showToast(MainActivity.this, "onClose"); // 让图标晃动 // mHeaderImage.setTranslationX(translationX) ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, "translationX", 15.0f); mAnim.setInterpolator(new CycleInterpolator(4)); mAnim.setDuration(500); mAnim.start(); } }); mLeftList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings){ @Override public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); TextView mText = ((TextView)view); mText.setTextColor(Color.WHITE); return view; } }); mMainList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES)) } }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-09-20 06:41:13

ViewDragHelper实现QQ侧滑效果的相关文章

ios开发-iOS 类型qq侧滑效果导航条不跟着变形

问题描述 iOS 类型qq侧滑效果导航条不跟着变形 开发-iOS 类型qq侧滑效果导航条不跟着变形-导航条跟着屏幕滚动"> 图片中的导航条不跟随这白色视图一起变形 请教下是什么原因 view.sendSubviewToBack(rightView) UIView.animateWithDuration(1) { () -> Void in self.centerVC!.view.center = CGPointMake(UIScreen.mainScreen().bounds.wid

Android_UI 仿QQ侧滑菜单效果的实现

相信大家对QQ侧滑菜单的效果已经不陌生了吧,侧滑进入个人头像一侧,进行对头像的更改,我的收藏,QQ钱包,我的文件等一系列的操作,今天呢,主要是实现进入侧滑菜单的这一效果原理进行分析. 主要思路分析 1.首先写一个SlideMenu 继承一个帧布局FrameLayout ,因为如果继承自ViewGroup的话,需要我们自己来实现onMeasure方法,而该方法的实现一般比较麻烦且没有必要,所以选择继承系统的已有的控件FrameLayout,不用其他控件是因为FrameLayout最轻量级 2.在布

Android程序开发之使用Design包实现QQ动画侧滑效果和滑动菜单导航_Android

Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的Android Design Support Library,在这个support库里面,Google给我们提供了更加规范的MD设计风格的控件.最重要的是,Android Design Support Library的兼容性更广,直接可以向下兼容到Android 2.2.这不得不说是一个良心之作. 使用方法很简单,只需要添加一句依赖 compile 'com.android

Android自定义view系列之99.99%实现QQ侧滑删除效果实例代码详解

首先声明本文是基于GitHub上"baoyongzhang"的SwipeMenuListView修改而来,该项目地址: https://github.com/baoyongzhang/SwipeMenuListView 可以说这个侧滑删除效果是我见过效果最好且比较灵活的项目,没有之一!!! 但是在使用它之前需要给大家提两点注意事项: 1,该项目支持Gradle dependence,但是目前作者提供的依赖地址对应的项目不是最新的项目,依赖过后的代码与demo中使用的不一致,会提示没有B

Android侧滑效果简单实现代码_Android

先看看效果: 首先,导入包:compile files('libs/nineoldandroids-2.4.0.jar') r然后在main中创建一个widget包. c创建ViewDragHelper类 public class ViewDragHelper { private static final String TAG = "ViewDragHelper"; public static final int INVALID_POINTER = -1 public static f

Android中自定义view实现侧滑效果_Android

效果图: 看网上的都是两个view拼接,默认右侧的不显示,水平移动的时候把右侧的view显示出来.但是看最新版QQ上的效果不是这样的,但给人的感觉却很好,所以献丑来一发比较高仿的. 知识点: 1.ViewDragHelper 的用法: 2.滑动冲突的解决: 3.自定义viewgroup. ViewDragHelper 出来已经比较久了 相信大家都比较熟悉,不熟悉的话google一大把这里主要简单用一下它的几个方法 1.tryCaptureView(View child, int pointerI

Android中自定义view实现侧滑效果

效果图: 看网上的都是两个view拼接,默认右侧的不显示,水平移动的时候把右侧的view显示出来.但是看最新版QQ上的效果不是这样的,但给人的感觉却很好,所以献丑来一发比较高仿的. 知识点: 1.ViewDragHelper 的用法: 2.滑动冲突的解决: 3.自定义viewgroup. ViewDragHelper 出来已经比较久了 相信大家都比较熟悉,不熟悉的话google一大把这里主要简单用一下它的几个方法 1.tryCaptureView(View child, int pointerI

qq侧滑

上一篇博客带大家实现了:Android 自定义控件打造史上最简单的侧滑菜单 ,有兄弟看了以后说,你这滑动菜单过时了呀~QQ5.0的效果还不错~~嗯,的确,上一篇也承诺过,稍微修改上一篇的代码,实现QQ5.0侧滑菜单~~好了,下面就开始为大家展示写一个类QQ的侧滑有多easy ~! 1.原理分析 首先对比一下我们上篇的实现距离QQ的效果还有多远: 差距还是蛮大的 区别1.QQ的内容区域会伴随菜单的出现而缩小 区别2.QQ的侧滑菜单给人的感觉是隐藏在内容的后面,而不是拖出来的感觉 区别3.QQ的侧滑

使用RecylerView完成拖动排序高仿qq侧滑删除功能_Android

 最近使用到Recylerview完成拖动排序,侧滑删除,在此记录一下. 需要使用到:ItemTouchHelper.Callback这个类. 效果图: 在有RecylerView的Activity中,除了设置Adapter还需要做的事情有: MyItemTouchHelperCallback callback = new MyItemTouchHelperCallback(adapter); //此类继承ItemTouchHelper.Callback,这是帮助处理RecylerView拖动侧