android之超级简单的下拉回弹--仿QQ个人主页

Posted 小钟视野

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android之超级简单的下拉回弹--仿QQ个人主页相关的知识,希望对你有一定的参考价值。

先看效果:
这里写图片描述
效果不错吧!

进入主题之前,先了解ImageView的scaleType的center_crop,网络上说的已经很清楚了 : 以下抄自网络:

  1. android:scaleType=”centerCrop”
    以填满整个ImageView为目的,将原图的中心对准ImageView的中心,等比例放大原图,直到填满ImageView为止(指的是ImageView的宽和高都要填满),原图超过ImageView的部分作裁剪处理。

    均衡的缩放图像(保持图像原始比例),使图片的两个坐标(宽、高)都大于等于 相应的视图坐标(负的内边距)。图像则位于视图的中央。 在XML 中可以使用的语法:android:scaleType=”centerCrop”。

不说废话,直接进入主题!!

思路

  1. 先将topView的布局和listview平级,然后将topview以及topview包裹的imageView中传listview,即一般是activity的layout
  2. 重写listView的ontoucEvent()方法,但不做任何拦截,只在action时,控制imageView以及topView的高度,使其重新layout然后重新布局就可以了。
  3. 以上是大概思路,这里具体分析:当action_down时记录其初始位置,action_move时得到dy,通过dy来判断是上啦还是下拉:
    (1)dy>0,则是下拉,不断重新设置topView和imageView的高度,又因为imageView的scaleType=center_crop,所以图片会按照这个规则进行等比拉伸,当到达图片最大时就会有不断放大的过程
    当松开手或者手指移出屏幕外时(action_up|action_outside|action_cancel)时让其回到初始位置,并伴着回弹过程,这里通过自定义动画让其具备回弹效果
    (2)dy<0,则是上拉,上推的过程,由于topView和Imageview不具备滚动的效果,所以上推也是通过控制topView和ImageView的高度,并且当TopVIew和ImageView滑出屏幕时就不在更改高度防止不断的绘制,提高性能。

ok,大体思路就这样。具体分析代码如下:

实现:

activity的xml
stretch_act.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:orientation="vertical">

    <!--这是topView-->
    <RelativeLayout
        android:id="@+id/rl_top"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >

        <!--这是imageView,一定要设置scaleType为centerCrop-->
        <ImageView
            android:id="@+id/iv_stretch_pic"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scaleType="centerCrop"
            android:src="@drawable/stretch_s"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/iv_stretch_pic"
            android:text="你最美,你最酷…………^^"
            android:textSize="16sp"/>

    </RelativeLayout>
<!--这是自定义的listview-->
    <com.example.zwr.myapplication.widget.StretchListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:cacheColorHint="@null"
        android:divider="@null"
        android:listSelector="#00000000"/>


</LinearLayout>

看看StretchListView:

/***
     * @param topView
     * @param imgResId 图片id
     */
    public void setTopView(View topView, int imgResId) {
        if (null != topView) {
            this.topView = topView;
            imageView = (ImageView) topView.findViewById(imgResId);
        }
    }

通过这个对外的方法,将topView以及ImageView的id传进来

分析:重新ListView的onTouchEvent():

ACTION_DOWN:
     case MotionEvent.ACTION_DOWN:
                startY = ev.getRawY();
                if (!hadInit) {//初始化,只要初始化一次就够了
                    childAt0Top = getChildAt(0).getTop();
                    ivInitHeight = imageView.getHeight();
                    hadInit = true;
                }
                break;

只是进行一些初始化操作:

 1. startY:相对于屏幕顶部的高度
 2. childAt0Top,获取listview的第一个view的top距离、
 3. ivInitHeight:获取ImageView的初始高度,即刚进来时的高度

ACTION_MOVE:

 case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "dy = " + dy);
                dy = ev.getRawY() - startY;
                if (dy > 0 && 0 == getFirstVisiblePosition() && 
                childAt0Top == getChildAt(0).getTop()) {//(1)手指从上往下拉:下拉
                    int tempDy = (int) (dy + 0.5);
                    //一定也要给topView增加一定的高度,否则从上啦到下拉就不会显示
                    imageView.getLayoutParams().height = imageView.getHeight() + tempDy;
                    topView.getLayoutParams().height = topView.getHeight() + tempDy;
                    topView.requestLayout();
                    isChangedHeight = true;

                } else {//(2)手指从下往上拉:上拉
                    int tempDy = (int) (dy - 0.5);
                    int currHeight = imageView.getHeight();
                    float translationY = getNegativeMaxValue(tempDy, -currHeight, 0);
                    if (translationY <= 0 && currHeight > 0) {
                        LinearLayout.LayoutParams lp = 
                        (LinearLayout.LayoutParams) topView.getLayoutParams();
                        //一定要减去titleBar,如果没有去掉Winow.xxx.Title,还要减去这个高度,否则会显示不全
                        lp.height = topView.getHeight() + (int) translationY;
                        topView.requestLayout();//
                        isChangedHeight = true;
                    }
                }
                //用这个getRawY而不是用getY,是因为listview也会随着改变,
                //而getY获取的就是listview本身的Y,所以基本是变化不大的,
                // 而使用getRawY相对于屏幕的距离,保证滑动了多大的距离就改变多大的距离
                startY = ev.getRawY();
                break;

当下拉时:主要条件如下:

  1. dy > 0 && 0 == getFirstVisiblePosition() && childAt0Top ==
    getChildAt(0).getTop()
    意思是当下拉时,并且listview的第一个位置显示全了,才能下拉放大图片,这是避免,listview已经发生滚动了,需要回到初始位置才能下拉放大,否则会出现,立即下拉放大,体验不好
  2. 当上拉时 主要条件
    if (translationY <= 0 && currHeight > 0)
    currHeight>0:当前ImageView的高度,如果已经滚动到顶部或者超出,则不再进行滚动,防止已经滚出屏幕不可视了,还在进行滚动。
    translationY<=0: 这个值是滚动的距离,这个距离不能超过ImageView的高度,由于上拉时dy是负值,所以要判断是否小于0;其主要方法如下:
  float translationY = getNegativeMaxValue(tempDy, -currHeight, 0);
   /***
     * 手指上移过程dy是负数
     * 返回负数最大值:0是最大值,不可以超过
     *
     * @param value           移动的最终距离:上次的位置+当次移动的偏移量之和,就是本次要移动的最终的偏移量
     * @param canMoveMaxValue 可移动的最大值
     * @param maxValue
     * @return
     */
    public static float getNegativeMaxValue(float value,float canMoveMaxValue, float maxValue) {
        return Math.min(maxValue, Math.max(canMoveMaxValue, value));
    }

ACTION_UP:

case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (isChangedHeight) {
                    if (imageView.getHeight() > ivInitHeight) {// (1)手指从上往下拉:下拉
                        ResetAnimation resetAnimation = 
                        new ResetAnimation(ivInitHeight, imageView, topView);
                        resetAnimation.setDuration(200);
                        imageView.startAnimation(resetAnimation);
                    } else {//(2)手指从下往上拉:上拉。。。这个不用处理。。。因为上拉后松开让其topview固定

                    }
                    isChangedHeight = false;
                }
                break;

isChangedHeight:当发生ImageView发生改变,并且是下拉时,这是松开手或者手指移出屏幕,则让其回弹到初始位置;这里是通过自定义动画来改变其变化的高度,达到回弹效果 代码如下

 /**
     * 自定义回弹动画,使imageView和topView过渡回弹到初始位置
     */
    static class ResetAnimation extends Animation {
        private View topView;
        private int topCurrHeight;

        private ImageView ivStretch;
        private int ivInitHeight;
        private int ivCurrHeight;

        public ResetAnimation(int ivInitHeiht, ImageView ivStretch, View topView) {

            this.ivInitHeight = ivInitHeiht;
            this.ivCurrHeight = ivStretch.getHeight();
            this.topCurrHeight = topView.getHeight();
            this.ivStretch = ivStretch;
            this.topView = topView;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            int dy = (int) ((ivCurrHeight - ivInitHeight) * interpolatedTime);
            Log.d(TAG, "anim dy = " + dy);
            ivStretch.getLayoutParams().height = ivCurrHeight - dy;
            topView.getLayoutParams().height = topCurrHeight - dy;
            topView.requestLayout();
        }
    }

其实主要是applyTransformation(float interpolatedTime, Transformation t) 这个方法
主要是通过这个渐变因子interpolatedTime来控制,其值范围是(0~1) 所以计算渐变的高度如下
int dy = (int) ((ivCurrHeight - ivInitHeight) * interpolatedTime);
然后一定要记得调用topView.requestLayout(),让其重新布局绘制。这样就完成了,所有代码,也就一百行代码左右,是不是很简单。而且通过这个demo,可以很好的拓展到scrollview中。

注意:
网上有些demo是通过overScrollBy()这个方法中搞事情,因为

/***
     * 这个方法是在滑出屏幕时回调,但是由于android系统国内厂商修改的面目全非,有些机型是不会回调的,比如vivo
     * 所以不要使用这个方法搞事情
     *
     * @param scrollX
     * @param scrollY
     */
    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, 
    int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, 
    boolean isTouchEvent) {
        Log.d(TAG, "deltax = " + deltaX + " deltaY = " + deltaY);
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
         scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }
  其自带dy,还有一些其它的参数,应有尽有。但是由于android系统是开源的导致有些系统是无法回调这个方法的,以至于无法实现回弹效果(比如:vivoX5)等等。所以在onTouchEvent()搞事情才是王道

ok!,以上有什么问题,请不吝指正!!!

Demo:http://download.csdn.net/detail/zhongwn/9765268

以上是关于android之超级简单的下拉回弹--仿QQ个人主页的主要内容,如果未能解决你的问题,请参考以下文章

Android仿苹果版QQ下拉刷新实现 ——打造简单平滑的通用下拉刷新控件

Android仿QQ登录下拉历史列表

Android自定义之高仿淘宝下拉刷新

安卓开发之Kotlin和java双实现仿qq空间下拉图片拉伸

自定义ViewpagerIndicator (仿猫眼,添加边缘回弹滚动效果)

仿格瓦拉@电影Android个人中心背景循环动图