ViewDragHelper实现QQ侧滑效果

作者:Kiven_Android 时间:2022-12-25 23:26:54 

前言

       侧滑的实现方式有很多方式来实现,这次总结的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))  
}
}

来源:http://blog.csdn.net/hpc19950723/article/details/70859917

标签:ViewDragHelper,QQ侧滑
0
投稿

猜你喜欢

  • Java调用groovy实现原理代码实例

    2023-05-16 16:41:51
  • Android监听输入法弹窗和关闭的实现方法

    2022-06-21 02:58:39
  • C#对称加密(AES加密)每次生成的结果都不同的实现思路和代码实例

    2022-05-24 21:56:35
  • 详解SpringBoot缓存的实例代码(EhCache 2.x 篇)

    2023-04-05 04:55:18
  • Java+Ajax实现的用户名重复检验功能实例详解

    2022-12-01 12:54:46
  • maven继承父工程统一版本号的实现

    2023-01-27 09:19:49
  • Java超详细分析@Autowired原理

    2023-11-25 05:37:44
  • 基于spring-boot和docker-java实现对docker容器的动态管理和监控功能[附完整源码下载]

    2022-02-04 00:41:18
  • springboot实现在工具类(util)中调用注入service层方法

    2021-06-17 20:02:51
  • java实现清理DNS Cache的方法

    2022-07-27 23:11:50
  • 基于WPF实现3D画廊动画效果的示例代码

    2022-02-06 08:02:22
  • C#及WPF获取本机所有字体和颜色的方法

    2022-12-23 01:23:13
  • Java 获取Web项目相对webapp地址的实例

    2022-07-03 17:46:00
  • Android开发 旋转屏幕导致Activity重建解决方法

    2022-03-02 06:00:23
  • springboot如何读取配置文件到静态工具类

    2023-11-28 04:44:54
  • Java网络编程实例——简单模拟在线聊天

    2023-08-20 04:39:42
  • Android 中对于图片的内存优化方法

    2023-11-23 12:24:41
  • Java环境配置与编译运行详解

    2022-10-02 12:42:24
  • 使用java的HttpClient实现多线程并发

    2022-09-19 20:35:55
  • C# XML字符串包含特殊字符的处理转换方法小结

    2022-08-17 07:21:24
  • asp之家 软件编程 m.aspxhome.com