仿QQ头部下拉放大效果,类似PullToZoomListview效果!

1.概述

看到最近有些人在问PullToZoomListview的效果,所以索性周末花了一天的时间写了这个效果.这是小弟第一次写博客,审美有限,排版什么的会比较渣,大家将就着看吧...

效果图:

由于没有模拟器,所以就用手机录的小视频截取的一小部分

2.第一步

首先大家都知道这是自定义的ListView,我也就不多说什么了(其实是不知道该说些什么).所以就直接讲讲思路然后上代码啦。

….想了半天连思路都不知道如何讲。。。我还是直接上代码吧

3.代码部分

首先我们需要一个listview然后为其添加header(如果不知道就先百度一下listview的header和footer),这里有个小提示,listview可以添加多个head和footer,并且添加了header后,listview中的item的position会发生变化.我画个图吧大致就是这样:

这里写图片描述
如果添加了2个头的话那就不用说了吧,footer同理(源码里可以看到因为header,footer也是item的一种)
题外话,此时如果设置OnItemClickListener的话,

Item item = myAdapter.getItem(position - headerCount);可以这样计算,还有一种更好的方式:
Item item = parent.getAdapter().getItem(position); 具体原因源码中有清晰的解释。

header.footer其实就2个要点: (1).listview可以添加多个header或者footer,(2)添加header后的position会发生变化,好了,哈哈,好像扯偏了,下面看代码:

PS:大家别喷我画的丑,如果有人能告诉我怎么画的好看就更好了…

public class MainActivity extends FragmentActivity {

    private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.listview);

        View header = View.inflate(this, R.layout.header_view ,null);
        listView.addHeaderView(header);

        ArrayAdapter<String> adapter = new ArrayAdapter<>(
                this,
                android.R.layout.simple_list_item_1,
                new String[]{"成都"});

        listView.setAdapter(adapter);
    }

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never"
        android:scrollbars="none"
        />
</RelativeLayout>

然后就是header的布局文件,其实就是一张图片:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/header_image"
        android:layout_width="match_parent"
        android:layout_height="@dimen/defalut_image_height"
        android:scaleType="centerCrop"
        android:src="@drawable/bg_reward_launcher"/>
</LinearLayout>

此时的效果图是这样的:
这里写图片描述

这个效果其实就一句话,改变Imagview的height高度.
好了接下来,要重写我们的listview了,首先我们要思考一个问题,既然我们要改变我们的Imagview的高度那我们是不是需要拿到Imageview控件,并且获取到Imagview的原始高度?
既然有了这个思路,我们就可以获取到Imagview并为其高度赋值了,接下来,咱们在重写的Listview中暴露一个方法将head中的Imagview传过去,代码如下:

ImageView iv = (ImageView) header.findViewById(R.id.header_image);
listView.setParallaxImageView(iv);
public class MyParallaxListView extends ListView{

    private ImageView mImage;
    private int imageHeight;

    public MyParallaxListView(Context context) {
        super(context);
    }

    public MyParallaxListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyParallaxListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setParallaxImageView(ImageView imageView){
        mImage = imageView;
        mImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
    }
}

好,这里咱们既然拿到了Imagview,那是不是可以直接通过imag.getHeight获取其高度了呢?当然不行,因为onCreate方法调用结束后才会调用控件的onMeasure以及onDraw方法(不知道原因的百度一下View的绘制流程,这里就不过多的赘述了),所以我们需要借助一个方法:

@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if(hasFocus){
            listView.setViewHeight();
        }
    }

当Activity的当前Window获得或失去焦点时会被回调此方法,有心的小伙伴可以打印一下各个方法的执行顺序,大致就是ativity.onCreate—–>activity.onResume——>view.onMeasure—->view.onLayout——->activity.onWindowFocusChanged()—–>view.onDraw();此时view的onMeasure方法已经被调用所以可以在这里获取到控件的宽高

  public void setViewHeight(){
        if(mImageHeight == -1){
            mImageHeight = mImage.getHeight();
            if(mImageHeight < 0){
                mImageHeight = getResources().getDimensionPixelSize(R.dimen.defalut_image_height);
            }
        }
    }

然后就是比较重要的地方了,首先需要知道这样一个方法:

 @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        //滑动过头的时候回调
        //使Imageview的高度不断改变
        /** deltaY
        *  当滑动过头时Y方向上的增量;
        *  往下拉过渡值为-
        *  往上拉过度值为+
        */
        boolean isOverScroll= isOverScrollBy(deltaY);

        //true 下拉到某个边界的时候不能再往下拉

        return isOverScroll? true : super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }

这个方法什么意思呢?查看源码注释可以看到它的意思就是说当listview滑动过头时的回调,英文不好就有道词典慢慢琢磨琢磨就好了.这些参数源码都有注释就不多说了,我们这里需要用到deltaY,这里的返回值如果返回true则代表下拉到某个位置时不能再继续下拉了(有心的小伙伴可以自己打印一下这些值)。

private boolean isOverScrollBy(int deltaY) {

        if(deltaY < 0){
            //不断改变Imagview的高度
            mImage.getLayoutParams().height = mImage.getHeight() - deltaY;
            //imageview重新调整宽高
            mImage.requestLayout();//---->onMeasure()以及onLayout
        }

        return false;
    }

其实现在已经实现了下拉图片会放大的效果,我们还有2个问题:
1.松开手在往上滑的时候图片的高度不变小
2.松开手时不弹回去

那现在有的小伙伴是不是会考虑当deltaY>0的时候呢?其实这里是错的,因为我们前面已经讲到了这个方法是当滑动过头的时候才会调用,此时放大图片后再往上滑是不会调用该方法的(想不明白多想想,然后运行一下之前写的看下效果就明白了).所以我们要考虑其他的方法

//监听滑动
@Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
    }

相信大家对这个方法都比较熟悉了,我就不再多说什么了,继续我们的剩下未完成的步骤:这里我们需要减小Imagview的高度,但是是在什么情况下呢?比较容易想到的一个条件就是现在的imagview高度大于初始高度时,即:mImage.getHeight() > mImageHeight.另一个条件就需要通过parent.getTop()了,先放代码,原因后面再讲

/**
     * 监听滑动
     * @param l
     * @param t
     * @param oldl
     * @param oldt
     */
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        View parent = (View) mImage.getParent();

        Log.d("parentTop",":"+parent.getTop());

        if(parent.getTop() < 0 && mImage.getHeight() > mImageHeight){

          LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mImage.getLayoutParams();
          //这里判断当imageview的高度小于原始高度的情况
          params .height = Math.max(imageView.getHeight() + parent.getTop(), imageHeight);
          //父容器的getTop()------->0
          parent.layout(parent.getLeft() ,0, parent.getRight(), parent.getHeight());
          imageView.setLayoutParams(params );
        }
    }

由于当前处于被拉大的状态,我们此时往上滑parent.getTop()是小于0的,因此我们还需要加上这么一个条件

parent.getTop() < 0 

注意这一句一定要加上:parent.layout(parent.getLeft() ,0, parent.getRight(), parent.getHeight());如果不加上这一句你就会发现往上滑动一下就直接恢复到了原始的状态

好了,写了这些是不是就认为大功告成了呢?运行一下发下,咦?为什么下拉之后往上滑,图片的高度不会改变呢?但是当我们多添加一些数据的时候 ,

ArrayAdapter<String> adapter = new ArrayAdapter<>(
                this,
                android.R.layout.simple_list_item_1,
                new String[]{"成都","上海","北京","深圳","香港","大连","哈尔滨","黑龙江","乌鲁木齐","西藏","云南","成都","上海","北京","深圳","香港","大连","哈尔滨","黑龙江","乌鲁木齐","西藏","云南"});

我们发现头部中的imageview的高度时会缩小的呀?为什么数据源很少的时候不会改变呢?
因为此时的Listview高度太短,自然不会调用onScrollChange()方法,所以我们需要换个思路.大家再仔细想一想,这个状态是不是就是滑动过头的状态???其实也就是说当Listview的高度比较矮的时候,此时会回调的方法是overScrollBy()这个方法,所以此时只需要在else里面操作就可以了,代码如下:

private boolean isOverScrollBy(int deltaY) {

        if(deltaY < 0){
            //不断改变Imagview的高度
            mImage.getLayoutParams().height = mImage.getHeight() - deltaY;
            //imageview重新调整宽高
            mImage.requestLayout();//---->onMeasure()以及onLayout
        }else {

            //不断改变Imagview的高度
            mImage.getLayoutParams().height = Math.max(mImage.getHeight() - deltaY, mImageHeight);
            //imageview重新调整宽高
            mImage.requestLayout();//---->onMeasure()以及onLayout
        }

        return false;
    }

接下来就是最后的松开手弹回的动画效果了,这个需要我们自定义Animation,代码如下

public class SmoothAnimation extends Animation{
        private ImageView iv;
        private int targetHeight;
        private int originaHeight;
        private int extraHeight;

        public SmoothAnimation(ImageView iv, int targetHeight) {
            this.iv = iv;
            this.targetHeight = targetHeight;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {

            super.applyTransformation(interpolatedTime, t);
        }
    }

这里,我们为什么要定义这几个参数呢?在了解这些之前,首先我们需要知道继承Animation后重写的
applyTransformation()方法中的参数代表什么意思,并且还需要知道为什么要重写这个方法。
其实每个动画都是继承了父类Animaition的applyTransformation()方法,这个方法的主要作用是把一些属性组装成一个Transformation类,这个方法会被父类的getTransformation方法调用.这里就不多做赘述了,想了解更多的自行百度Android动画的实现原理.既然现在我们知道了为什么要继承这个方法,那只用知道这两个参数的意思是什么就可以了.其实,查看源码就可以清楚的知道它们的意思.

/**
     * Helper for getTransformation. Subclasses should implement this to apply
     * their transforms given an interpolation value.  Implementations of this
     * method should always replace the specified Transformation or document
     * they are doing otherwise.
     * 
     * @param interpolatedTime The value of the normalized time (0.0 to 1.0)
     *        after it has been run through the interpolation function.
     * @param t The Transformation object to fill in with the current
     *        transforms.
     */
    protected void applyTransformation(float interpolatedTime, Transformation t) {
    }

这里第一个float类型的参数interpolatedTime意思其实就是一个类似时间的百分比,是从0.0-1.0,假如我设置的动画时长为300ms,那么在150ms的时候找个值就是0.5,这里我们只需要用到第一个参数.
整个动画的代码如下:

public class SmoothAnimation extends Animation{
        private ImageView iv;
        private int targetHeight;
        private int originalHeight;
        private int extraHeight;

        public SmoothAnimation(ImageView iv, int targetHeight) {
            this.iv = iv;
            this.targetHeight = targetHeight;
            originalHeight = iv.getHeight();

            extraHeight = originalHeight - targetHeight;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {

            /**
             * 动画不断执行的时候回调
             * interpolatedTime:范围 0-1
             * 0ms----------------->300ms
             * 当前图片的高度---------->动画执行之前的高度-高度差*interpolatedTime
             *
             * extraHeight------------>0
             */

            iv.getLayoutParams().height = (int) (originalHeight - extraHeight * interpolatedTime);
            iv.requestLayout();

            super.applyTransformation(interpolatedTime, t);
        }
    }

在哪里执行我就不用说太多了吧.ouTouch方法的up事件执行改动画:

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:

                SmoothAnimation animation = new SmoothAnimation(mImage, mImageHeight);
                animation.setDuration(300);
                mImage.startAnimation(animation);
                break;
        }
        return super.onTouchEvent(ev);
    }

好了,这次就写到这里吧,不知不觉都2点钟了,其实我现在唯一的想法就是以后再也不要写博客了!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值