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

Posted xingxing_yan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于SlideCloseLayout—仿头条多图预览的页面关闭效果的主要内容,如果未能解决你的问题,请参考以下文章

android 仿头条 微信大图预览动画 双击缩放 保存至相册

Android 仿朋友圈之九宫格多图显示

项目记录,仿今日头条app

vue 仿今日头条

vue 仿今日头条

iOS仿今日头条滑动导航