Android实现QQ空间图片下拉变大效果(雷惊风)

  • Post author:
  • Post category:其他



当我们打开QQ控件时,向下滑动他头部的图片,你会发现有变大的效果,这个效果实现起来并不难,下面咱们一起来实现一下。


首先我们分析一下,因为QQ空间里除了这张图片下边还有很多我们好友发布的消息甚至是比好友发的消息还多的广告,而且是一条一条的,包含了不同的布局,有的有图片,有的都是纯文本,有的既有文本又有图片等等等等,其实这些就是简单的ListView通过给头部添加包含图片的Header布局,每一个子项使用不同布局实现的,记得早期的时候我也曾经开发过类似的应用。那么,今天咱们也就通过自定义ListView来实现QQ空间顶部图片下拉变大的效果。


好了,找好了控件,那么我们接下来分析一下需要实现的功能,一步一步来啊,首先就是下拉变大效果,来吧,开始撸码,我们继承ListView来自定义控件,实现内部构造方法,同时,在View中有一个overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent),这个方法会在View滑动超出范围时被系统调用,这里我们用它的第二个参数,因为是上下滑动,所以只关心Y轴就可以了,当用户向下滑动ListView,露出顶端并继续向下滑动时就会调用当前方法,deltaY为负数,当用户向上滑动ListView时,露出底部并继续向上滑动时同样也会调用当前方法,deltaY为正数。我们可以通过这个方法来做些手脚。首先做一下前期准备工作,一个ListView的header布局:


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

    <ImageView
        android:id="@+id/img_header"
        android:layout_width="match_parent"
        android:layout_height="@dimen/header_drawable_height"
        android:scaleType="centerCrop"
        android:src="@drawable/timg" />
</LinearLayout>


注意这里

ImageView的height是写死的200dp,并且通过src属性定义图片,scaleType为centerCrop。ListView中每一个子项布局,代码如下:

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

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center"
        android:text="中国人"
        android:textSize="22dp" />
</LinearLayout>

这里没什么,就一个TextView控件。MyListView自定义控件代码:

public class MyListView extends ListView {
    private ImageView mHeaderImg;

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

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

    public void setHeaderViewImg(ImageView img) {
        this.mHeaderImg = img;
    }

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        if (deltaY < 0) {
            mHeaderImg.getLayoutParams().height = mHeaderImg.getHeight() - deltaY;
            mHeaderImg.requestLayout();
        }
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }
}

这里我继承了ListView来实现相关功能,内部定义了一个mHeaderImg的ImageView成员变量,这个变量用来保存外边传递进来的HeaderView中要被放大的ImageView,也就是通过setHeaderViewImg(ImageView img)方法传递进来的,然后重写了overScrollBy()方法,在方法中通过判断deltaY<0时代表用户下拉超过范围,那么需要给mHeaderImg改变高度,并且重新测量绘制。再看一下MainActivity中的代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyListView myListView = (MyListView) findViewById(R.id.myListView);
        ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item_listview, R.id.tv, new String[]{
                "中国人", "美国人", "瑞典人", "俄罗斯人", "日本人", "德国人", "意大利人", "法国人", "英国人", "智利人",
                "俄罗斯人", "日本人", "德国人", "意大利人", "法国人", "英国人", "智利人"
        });
        View headerView = getWindow().getLayoutInflater().inflate(R.layout.header, null);
        ImageView img = (ImageView) headerView.findViewById(R.id.img_header);
        myListView.setHeaderViewImg(img);
        myListView.addHeaderView(headerView);
        myListView.setAdapter(adapter);
    }

}

好了,准备工作做完了,看一下执行效果:


咦,不错,向下滑,图片放大了,但是你会发现,当我们滑下来图片变大后在向上滑动时,图片没有恢复,而是直接向上滑动了,那么我们怎么让他恢复呢,这里肯定不能在overScrollViewBy()中处理了,因为没有超出滑动范围,所以不会执行方法,在这里,我们实现onScrollChanged()方法,如下:

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    View vParent= (View) mHeaderImg.getParent();
    if(mHeaderImg.getHeight()>mDrawAbleHeight&&vParent.getTop()<0){
        mHeaderImg.getLayoutParams().height = mHeaderImg.getHeight() + vParent.getTop();
        vParent.layout(0,0,mHeaderImg.getWidth(),mHeaderImg.getHeight());
        mHeaderImg.requestLayout();
    }
    super.onScrollChanged(l, t, oldl, oldt);
}

首先获取了放大图片的外层布局,判断当前图片高度是否大于原始图片高度,并且外层布局的getTop()值小于0(表示向上滑动时滑出屏幕了),如果满足条件,则改变图片高度,这里需要添加的图片高度为外层布局到top的距离,因为为负值,所以加上。然后因为我们的图片高度变了,所以父布局重新测量,图片也要重新测量。看一下运行效果:


是不是解决了咱们不能恢复的问题。完美,是不是小伙伴们,如果你感觉是,那就错了,其实咱们的项目还是有bug的,我们将数据删除一部分,看一下:


你会发现当我们条目比较少的时候下拉会放大,但是在下边没有超出屏幕时,再次上滑,图片不会恢复,这里因为整个ListView能够完全显示,所以不会执行滑动,也就不会执行onScrollChanged()方法,所以这里我们还是在overScollBy()方法中做手脚,我们加上一个else判断就可以了,如下:

@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
    if (deltaY < 0) {
        mHeaderImg.getLayoutParams().height = mHeaderImg.getHeight() - deltaY;
        mHeaderImg.requestLayout();
    }else if (deltaY >= 0 && mHeaderImg.getHeight() > mDrawAbleHeight) {
        mHeaderImg.getLayoutParams().height = mHeaderImg.getHeight() - deltaY;
        //高度虽然改变了,必须调用重新测量;
        mHeaderImg.requestLayout();
    }
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}



再看一下效果:




又一次解决了问题。到这里还有一个问题,就是在QQ控件我们拉动松开时有一个会弹的效果,我们也加一下,我们需要监听抬起动作,所以这里我们需要重写onTouchEvent(MotionEvent ev)看一看代码实现:

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_UP:
            if (mHeaderImg.getHeight() > mDrawAbleHeight) {
                MyAnimation myAnimation = new MyAnimation(mHeaderImg, mDrawAbleHeight);
                myAnimation.setDuration(200);
                mHeaderImg.startAnimation(myAnimation);
            }
            break;
    }

    return super.onTouchEvent(ev);
}

也很简单,就是当我们抬起手指时判断当前图片高度是否大于原始图片高度,如果大于,那么我在这里自定义一个动画Animation,设置执行时间为200毫秒,开始动画,可以看到在初始化动画时,我传入了当前图片,与图片原始大小,看一下自定义Animation的具体实现:

public class MyAnimation extends Animation {
    private int nowImgHeight;
    private int imgHeightDiffer;

    public MyAnimation(ImageView img, int initedImgHeight) {
        nowImgHeight = img.getHeight();
        imgHeightDiffer = nowImgHeight - initedImgHeight;

    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        mHeaderImg.getLayoutParams().height = (int) (nowImgHeight - imgHeightDiffer * interpolatedTime);
        //高度虽然改变了,必须调用重新测量;
        mHeaderImg.requestLayout();
        super.applyTransformation(interpolatedTime, t);
    }
}

主要是重写了applyTransFormation()方法,俩个参数我们在这里只用第一个,他表示当前动画执行程度,从0到1的一个变化过程,了解了这些我们就可以在这里边改变图片高度了,首先我们求出在规定的200毫秒内需要移动的距离,也就是当前高度减去原高度就是我们在200毫秒内需要移动的距离,让当前距离减去当前时间的一个执行程度的高度就等于图片当前的一个高度,然后重绘当前图片。好了,一切都好了,执行一下看一下效果吧:


到这里,整个功能就完成了,其实还可以做很多操作,比如放大到一定程度就不能在放大,header中添加一个小图片,执行动画,实现类似微信中下拉旋转效果等等,大家都可以尝试着实现一下,

点击现在源码

。好了,今天就到这里,悲剧的自己还在加班,今天版本上线,本人bug已经全部解决完,但是也不能回家,只能等着其他同事解决完bug上完线才能走,抽点时间整理点小东西分享一下挺好的,不是说上完线了吗,怎么还在讨论,啥情况,兄弟们,在讨论就两点了,悲剧!



版权声明:本文为liuyonglei1314原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。