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

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

注:此项目是参考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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值