7.侧滑、ViewDragHelper、属性动画

实现这样的效果:

## 侧滑面板(对ViewGroup的自定义) * 应用场景: 扩展主面板的功能 * 功能实现: > 1. ViewDragHelper: Google2013年IO大会提出的, >  解决界面控件拖拽移动问题. (v4包下) > 2. mTouchSlop 最小敏感范围, 值越小, 越敏感 * 伴随动画: > 1. 左面板: 缩放动画, 平移动画, 透明度动画 > 2. 主面板: 缩放动画 > 3. 背景动画: 亮度变化 (颜色变化) * 状态监听\触摸优化: > 1. 设置并更新状态 > 2. 触摸优化: 重写ViewGroup里onInterceptTouchEvent和onTouchEvent 新v4、看大小 nineoldandroids.jar  属性动画,兼容9个低版本 ActionBarSherlock 

布局:

<com.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.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.drag.MyLinearLayout>
</com.drag.DragLayout>

Utils:单例的toast

public class Utils {
 public static Toast mToast;
 public static void showToast(Context mContext, String msg) {
 if (mToast == null) {
 mToast = Toast.makeText(mContext, "", Toast.LENGTH_SHORT);
 }
 mToast.setText(msg);
 mToast.show();
 }
 /**
 * dip 转换成 px
 * @param dip
 * @param context
 * @return
 */
 public static float dip2Dimension(float dip, Context context) {
 DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
 }
 /**
 * @param dip
 * @param context
 * @param complexUnit {@link TypedValue#COMPLEX_UNIT_DIP} {@link TypedValue#COMPLEX_UNIT_SP}}
 * @return
 */
 public static float toDimension(float dip, Context context, int complexUnit) {
 DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
 return TypedValue.applyDimension(complexUnit, dip, displayMetrics);
 }
 /** 获取状态栏高度
 * @param v
 * @return
 */
 public static int getStatusBarHeight(View v) {
 if (v == null) {
 return 0;
 }
 Rect frame = new Rect();
 v.getWindowVisibleDisplayFrame(frame);
 return frame.top;
 }
 public static String getActionName(MotionEvent event) {
 String action = "unknow";
 switch (MotionEventCompat.getActionMasked(event)) {
 case MotionEvent.ACTION_DOWN:
 action = "ACTION_DOWN";
 break;
 case MotionEvent.ACTION_MOVE:
 action = "ACTION_MOVE";
 break;
 case MotionEvent.ACTION_UP:
 action = "ACTION_UP";
 break;
 case MotionEvent.ACTION_CANCEL:
 action = "ACTION_CANCEL";
 break;
 case MotionEvent.ACTION_SCROLL:
 action = "ACTION_SCROLL";
 break;
 case MotionEvent.ACTION_OUTSIDE:
 action = "ACTION_SCROLL";
 break;
 default:
 break;
 }
 return action;
 }
}

DragLayout:

/**
 * 侧滑面板
 * @author poplar
 *
 */
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);
 }
 /**
 * 估值器,0-100,一半50百分之=50,10-100
 * @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);
 }
}

MyLinearLayout:主页面,在打开侧边栏或者拖拽时不让主页面里的listview滑动

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

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){//Cheeses自己定义的,存放的一些字符串
 @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));
 }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏androidBlog

自定义View常用例子二(点击展开隐藏控件,九宫格图片控件)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

1111
来自专栏Sorrower的专栏

一起来做个拜年App吧!

972
来自专栏计算机编程

点击显示更多文本自定义控件

写在前面的话: 在正常项目流程中,我们很多情况下会碰到点击显示更多文本,这样可以利于页面变化加载,点击显示更多可能会非常常用,现在博主利用自己的闲暇时间来一点一...

1503
来自专栏移动开发

FruitLoadView 一个自定义view可用来做加载view

Github地址:https://github.com/X-FAN/FruitLoadView 欢迎star

892
来自专栏李蔚蓬的专栏

实战 | 使用揭露动画(Reveal Effect)做一个丝滑的Activity转场动画

最近跟几个小伙伴在实践一个项目,考虑到界面效果,我们决定使用揭露动画作为Activity的转场动画。

2513
来自专栏潇涧技术专栏

Android Heroes Reading Notes 2

《Android群英传》读书笔记 (2) 第三章 控件架构与自定义控件详解 + 第四章 ListView使用技巧 + 第五章 Scroll分析

941
来自专栏青蛙要fly的专栏

项目需求讨论-仿ios底部弹框实现及分析

hi,在项目开发中,有时候需要仿照ios的底部弹框做效果,比如我们在iPhone上面关闭定位的时候,就会弹出ios特有的底部弹框:

1243
来自专栏Android学习之路

ViewDragHelper使用笔记及侧滑菜单实践

3356
来自专栏程序员叨叨叨

【Android】手把手教你上滑解锁的效果

最近,公司开发的APP中要实现类似上滑解锁效果的推荐页,捣腾了两天,基本实现了效果,附效果图如上。接下来和大家聊聊如何实现这样的效果。

5272
来自专栏Android相关

RecyclerView的NestedScroll实现

1422

扫码关注云+社区

领取腾讯云代金券