SlideCloseLayout—仿头条多图预览的页面关闭效果

来源:互联网 发布:mui vue.js 集成 编辑:程序博客网 时间:2024/06/02 13:16

最近同事问头条多图预览界面的关闭效果怎么实现,之前没有怎么注意,查看之后,顿时发现这个关闭效果还挺有意思,于是决定弄上一弄!那就撸起袖子开始吧!

注:此项目是参考SwipableLayout 控件做的,如有侵权,请联系我(1547740082)

一. 功能概述

  1. 在上滑或者下滑时,随着手指的移动,图片区域跟随移动,并且activity的背景逐渐变的透明
  2. 在滑动距离不超过一段范围时,会有回弹效果。
  3. 在滑动超过设置的范围时,放开手指,页面自动滑动消失
  4. 在按返回键后,页面结束动画跟手指下滑一样。
效果图

手指触摸退出

按返回键退出

二.SlideCloseLayout的实现

1. 拦截触摸事件

我们知道,View的触摸事件默认从最上层往最下层去传的,当垂直滑动SlideCloseLayout时,需要移动其自身的位置,所以在垂直滑动时,需要拦截触摸事件,不传给子View,而是自己处理。

public boolean onInterceptTouchEvent(MotionEvent ev) {        if (isLocked) {            return false;        } else {            final int y = (int) ev.getRawY();            final int x = (int) ev.getRawX();            switch (ev.getAction()) {                case MotionEvent.ACTION_DOWN:                    previousX = x;                    previousY = y;                    break;                case MotionEvent.ACTION_MOVE:                    int diffY = y - previousY;                    int diffX = x - previousX;                    if (Math.abs(diffX) + 50 < Math.abs(diffY)) {                        return true;                    }                    break;            }            return false;        }    }

先解释一下isLocked的作用,当isLocked为true时,就是禁止SlideCloseLayout有滑动效果。我们在移动时判断,如果y方向的滑动距离大于x+50,则认为就是垂直方向的滑动,此时返回true,拦截事件。

2. 处理触摸事件

(1) 当事件被拦截后,就需要SlideCloseLayout在onTouchEvent中去处理,先看移动时怎么处理,如下:

public boolean onTouchEvent(@NonNull MotionEvent ev) {        if (!isLocked) {           ...            switch (ev.getAction()) {               ...                case MotionEvent.ACTION_MOVE:                    int diffY = y - previousY;                    int diffX = x - previousX;                    //判断方向                    if (direction == Direction.NONE) {                        if (Math.abs(diffX) > Math.abs(diffY)) {                            direction = Direction.LEFT_RIGHT;                        } else if (Math.abs(diffX) < Math.abs(diffY)) {                            direction = Direction.UP_DOWN;                        } else {                            direction = Direction.NONE;                        }                    }                    //当方向为垂直方向时,移动布局并改变透明度                    if (direction == Direction.UP_DOWN) {                        isScrollingUp = diffY <= 0;                        this.setTranslationY(diffY);                        if (mBackground != null){                            int alpha = (int) (255 * Math.abs(diffY * 1f)) / getHeight();                            mBackground.setAlpha(255 - alpha);                        }                        return true;                    }                    break;                case MotionEvent.ACTION_UP:                    ...            }            return true;        }        return false;    }

首先,如果SlideCloseLayout被锁定,则不做任何操作。在ACTION_MOVE中,根据水平和垂直的滑动距离判断滑动的方向。当方向为direction == Direction.UP_DOWN时,通过setTranslationY()方法设置控件的移动距离,并根据滑动的距离计算背景透明度的变化。透明度如何计算?根据滑动距离diffY和控件高度height的比例,算出0-255之间的透明度alpha,由于背景是从不透明到透明,所以还需要用255-alpha,设置给mBackground。

(2) 当滑动一定距离,手指松开时,我们需要做两个处理,判断距离是否大于我们设置的阈值(高度的1/7),如果大于,则退出,否则恢复。如下:

 case MotionEvent.ACTION_UP:     if (direction == Direction.UP_DOWN) {         int height = this.getHeight();         //判断滑动距离是否大于height/7         if (Math.abs(getTranslationY()) > (height / 7)) {              //执行退出动画              layoutExitAnim(600, true);          } else {              //执行恢复动画              layoutRecoverAnim();          }          direction = Direction.NONE;          return true;      }

退出动画

/**     * 退出布局的动画     * @param duration 动画时长     * @param isFingerScroll   是否手指滑动触发     */    public void layoutExitAnim(long duration, boolean isFingerScroll){        ObjectAnimator exitAnim;        if (isFingerScroll){            exitAnim = ObjectAnimator.ofFloat(this, "translationY", getTranslationY(), isScrollingUp ? -getHeight() : getHeight());        }else{            exitAnim = ObjectAnimator.ofFloat(this, "translationY", 0, getHeight());        }        exitAnim.addListener(new AnimatorListenerAdapter() {            @Override            public void onAnimationEnd(Animator animation) {                if (mBackground != null){                    mBackground.setAlpha(0);                }                if (mScrollListener != null) {                    mScrollListener.onLayoutClosed();                }            }        });        exitAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                if (mBackground != null){                    int alpha = (int) (255 * Math.abs(getTranslationY() * 1f)) / getHeight();                    mBackground.setAlpha(255 - alpha);                }            }        });        exitAnim.setDuration(duration);        exitAnim.start();    }

判断是手指滑动退出还是按返回键退出,以此来设置动画需要移动的距离,同时根据isScrollingUp设置动画的方向。动画执行中,背景的透明度还是在继续变化的,所有需要给exitAnim设置AnimatorUpdateListener监听,在每次变化后让透明度跟着变化。当动画执行完后,先设置背景的透明度为0,然后调用回调监听LayoutScrollListener中的onLayoutClosed()方法,在Activity中,实现此接口,在onLayoutColsed()中关闭Activity。

恢复动画

/** * 恢复动画 */ private void layoutRecoverAnim(){        ObjectAnimator recoverAnim = ObjectAnimator.ofFloat(this, "translationY", this.getTranslationY(), 0);        recoverAnim.setDuration(100);        recoverAnim.start();        if (mBackground != null){            mBackground.setAlpha(255);        }    }

如果滑动的距离没有大于设置的阈值,则SlideCloseLayout需要恢复到初始位置,同时透明度也需要恢复到不透明,所以mBackground.setAlpha(255)。

到此,SlideCloseLayout控件就实现完成了,接下来就去使用吧。

三. 使用

  1. 布局文件
<?xml version="1.0" encoding="utf-8"?><com.yyx.library.SlideCloseLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/scl"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <com.yyx.slidecloselayout.widget.TouchViewPager        android:id="@+id/scl_pager"        android:layout_width="match_parent"        android:layout_height="match_parent">    </com.yyx.slidecloselayout.widget.TouchViewPager></com.yyx.library.SlideCloseLayout>

2 SlideCloseLayoutActivity:

public class SlideCloseLayoutActivity extends AppCompatActivity {    private SlideCloseLayout mSlideCloseLayout;    private ViewPager mPager;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_slide_close_layout);        //设置activity的背景为黑色     getWindow().getDecorView().setBackgroundColor(Color.BLACK);        mSlideCloseLayout = (SlideCloseLayout) findViewById(R.id.scl);        mPager = (ViewPager) findViewById(R.id.scl_pager);        //给控件设置需要渐变的背景。如果没有设置这个,则背景不会变化        mSlideCloseLayout.setGradualBackground(getWindow().getDecorView().getBackground());        //设置监听,滑动一定距离后让Activity结束        mSlideCloseLayout.setLayoutScrollListener(new SlideCloseLayout.LayoutScrollListener() {            @Override            public void onLayoutClosed() {                onBackPressed();            }        });        CustomPagerAdapter adapter = new CustomPagerAdapter(this);        mPager.setAdapter(adapter);    }   ...}

先给Activity设置黑色背景,然后件Activity的背景传给控件, mSlideCloseLayout.setGradualBackground(getWindow().getDecorView().getBackground()),这个其实就是给SlideCloseLayout中的mBackground赋值,如果不传则没有背景的变化效果。然后设置LayoutScrollListener监听,并在onLayoutClosed()中执行onBackPressed(),退出Activity。

3 返回键的处理
返回键我并没有像头条那样覆盖Activity的退出动画,在style中直接设置activityCloseExitAnimation这个属性退出时动画不会起作用,必须重写Activity的finish()方法,然后在super.finish()之后执行overridePendingTransition()方法才会起作用,感觉好烦的样子。我的实现方式是直接调用SlideCloseLayout中的退出动画方法layoutExitAnim(),只需要把参数isFingerScroll传为false即可,不用额外的去设置Activity的退出动画。如下:

 @Override    public boolean onKeyDown(int keyCode, KeyEvent event) {        if (keyCode == KeyEvent.KEYCODE_BACK){            mSlideCloseLayout.layoutExitAnim(1000, false);            return true;        }else{            return super.onKeyDown(keyCode, event);        }    }

4 Activity样式设置:

<style name="SlideCloseTheme" parent="Theme.AppCompat.Light.DarkActionBar">        <item name="windowNoTitle">true</item>        <item name="android:windowIsTranslucent">true</item>        <item name="android:windowBackground">@android:color/transparent</item>        <item name="android:windowNoTitle">true</item>        <item name="windowActionBar">false</item>    </style>

AndroidManifest.xml:

 <activity android:name=".activity.SlideCloseLayoutActivity"            android:theme="@style/SlideCloseTheme" />

至此,整个SlideCloseLayout的实现和使用就介绍完,有不好的地方欢迎大神指正!

源码:https://github.com/xingxing-yan/SlideCloseLayout

0 0
原创粉丝点击