Android高仿今日头条/QQ空间手势下拉关闭图片效果

前言


最近有个需求,要做成今日头条的效果,我就下载了今日头条的app看了看,发现效果简单,体验也还不错,最近几天发现QQ空间安卓版也有这个效果,当然,牛逼的微信早就有这个效果了,而且微信的图片随着手势下拉是图片会缩放的并且有入场和退出的动画,交互比头条和QQ的多了两个动画,其实我觉得交互越简单对于用户来说越好,我反而觉得头条和QQ空间的效果不错,所以今天就来实现下今日头条的这个效果.


今日头条和QQ空间的效果图


  


我最终实现的效果图


 



需求分析


思考一下,其实不难,总共就没有几个动画,我是在项目用的缩放控件上改的,这样项目改动最小,这样改一个类所有涉及到的类就全都改了.

1.可以看到头条点击图片时首先有淡入淡出的动画,这个就用overridePendingTransition 转场动画就行,安卓2.0以上就支持,在startActivity() 或者 finish后面加上转场动画 fade_in,fade_out,淡入淡出的效果就实现了.
2.头条和QQ空间的大图浏览时,双手放大时是不会拖动该图片的,因此,当图片没有被放大时,同时只有一个手指才会触发上下拖动这个事件.
3.可以看到上下拖动状态时,背景透明度跟着改变,首先大图浏览模式下的类Activity必须是透明的主题,这个效果才能实现,即把配置文件中把Activity的主题使用透明的主题即可.
4.手势拖动使用ViewHelper类,即操作View动画类的方法集合,gradle里面远程依赖com.nineoldandroids:library:2.4.0 即可.


核心代码


根据上述的分析,我想大家都跃跃欲试了,其实真的不难,最好都代码编写下,给自己的项目也加个效果.

1.首先要让当前View动起来


 根据上面的分析,用到了ViewHelper.其上下拖动的触发条件是 当前的scale 没有放大,同时fingerTouch = 1即一个手指的情况下,触发拖动的事件.if(currentScale <= minscale() && touchCount == 1) ----> onSingleActionMove(event);


2.onSingleActionMove方法


这里边主要是透明度的算法,透明度是从0到1,view移动的范围是MAX_TRANSLATE_Y 这里透明度的计算我又加上了图片高度,防止透明度的跨度过大,View的移动距离[0,MAX_TRANSLATE].
则百分比 percent = 移动的距离(mTranslateY) / MAX_TRANSLATE_Y;
同理 alpha 透明度的范围是[1,0],即从不透明到透明的过程,则变量为 mAlpha = 1 - percent;
则代码编写如下:

private void onSingleActionMove(MotionEvent event) {
        isOneFingerDrag = true;
        float moveX = event.getRawX();
        float moveY = event.getRawY();
        mTranslationX = moveX - mDownX + mLastTranslationX;
        mTranslationY = moveY - mDownY + mLastTranslationY;
        float percent = Math.abs(mTranslationY / (MAX_TRANSLATE_Y + sHeight));
        Log.e(TAG, "percent= " + percent + "translationY= " + mTranslationY + "Max_Y = " + MAX_TRANSLATE_Y);
        LinearLayout linearLayout = (LinearLayout) getParent();
        float mAlpha = (1 - percent);
        if (mAlpha > 1) {
            mAlpha = 1;
        } else if (mAlpha < 0) {
            mAlpha = 0;
        }
        Log.e(TAG, "alpha = " + mAlpha);
        if (null != linearLayout) {
            linearLayout.getBackground().mutate().setAlpha((int) (mAlpha * 255));
        }
        ViewHelper.setTranslationY(this, mTranslationY);
    }

3.callBack 回归原位的动画方法


当移动距离mTranslateionY 小于 可退出的最大值时,则让其回归原位,其移动范围为[mTransltionY,0]
使用ValueAnimator 线性回归,编写代码如下:

private void resetCallBackAnimation() {
        ValueAnimator animatorY = ValueAnimator.ofFloat(mTranslationY, 0);
        animatorY.setDuration(DURATION);
        animatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                if (isAnimate) {
                    mTranslationY = (float) valueAnimator.getAnimatedValue();
                    mLastTranslationY = mTranslationY;
                    ViewHelper.setTranslationY(SubsamplingScaleImageView2.this, mTranslationY);
                }
            }
        });
        animatorY.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                isAnimate = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (isAnimate) {
                    mTranslationY = 0;
                    LinearLayout linearLayout = (LinearLayout) getParent();
                    if (null != linearLayout) {
                        linearLayout.getBackground().mutate().setAlpha(255);
                    }
                    invalidate();
                    reset();
                }
                isAnimate = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        animatorY.start();
    }

4.退出并关闭当前界面动画

  当mTranslationY > MAX_TRANSLATE_Y 时 则从当前位置退出,它的运动轨迹为[mTranslationY,getHeight()]
  当向上滑动退出时,mTranslationY 为负值,则其运动轨迹为[mTranslationY,-getHeight()]; 
  使用的均是ValueAnimator动画退出.
  
public void exitWithTranslation(float currentY) {
        float translationY;
        float imgHeight = sHeight;
        if (currentY > 0) {
            ValueAnimator animDown = ValueAnimator.ofFloat(mTranslationY, getHeight());
            animDown.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float fraction = (float) animation.getAnimatedValue();
                    Log.e(TAG, "fraction==" + fraction);
                    ViewHelper.setTranslationY(SubsamplingScaleImageView2.this, fraction);
                }
            });
            animDown.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    reset();
                    ((Activity) getContext()).finish();
                    ((Activity) getContext()).overridePendingTransition(0, 0);
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
            animDown.setDuration(DURATION);
            animDown.setInterpolator(new LinearInterpolator());
            animDown.start();
        } else {
            ValueAnimator animUp = ValueAnimator.ofFloat(mTranslationY, -getHeight());
            animUp.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float fraction = (float) animation.getAnimatedValue();
                    Log.e(TAG, "fraction==" + fraction);
                    ViewHelper.setTranslationY(SubsamplingScaleImageView2.this, fraction);
                }
            });
            animUp.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    reset();
                    ((Activity) getContext()).finish();
                    ((Activity) getContext()).overridePendingTransition(0, 0);
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
            animUp.setDuration(DURATION);
            animUp.setInterpolator(new LinearInterpolator());
            animUp.start();
        }
    }



遇到的坑


透明度改变时,当前View的透明度跟着变化,让人头疼的问题.
使用下面代码解决,让其不共享透明度的变化.
 linearLayout.getBackground().mutate().setAlpha(255);


2018.01.18更新
1.使用setScrollY替代setTranslateY优化动画体验.
2.增加仿微信朋友圈下拉的动画效果.






欢迎star 和 fork,谢谢!





发布了60 篇原创文章 · 获赞 109 · 访问量 36万+

猜你喜欢

转载自blog.csdn.net/shenshibaoma/article/details/78468810