自定义控件:视差特效

ParallaxEffects 视差特效

  • 了解ImageView 的scaleType 属性
  • 掌握ListView 的overScrollBy()方法

应用场景:QQ 空间,微信朋友圈,微博,需要快速定位的列表效果图

界面初始化

填充ListView

自定义ParallaxListView 继承ListView

public class ParallaxListView extends ListView {
    public ParallaxListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    public ParallaxListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public ParallaxListView(Context context) {

        super(context);
    }
}

填充ListView 的数据

public class Cheeses {
    public static final String[] NAMES = new String[]{"宋江", "卢俊义", "吴用",
            "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智 深",
            "武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", " 穆弘",
            "雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", " 解珍",
            " 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪 ",
            "魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方 ",
            "郭盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", " 项充",
            "李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿 ",
            "陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永 ", "施恩",
            "周通", "李忠", "杜兴", "汤隆", "邹渊", "邹润", "朱富", "朱贵", "蔡福", "蔡庆", " 李立",
            "李云", "焦挺", "石勇", "孙新", "顾大嫂", "张青", "孙二娘", " 王定六", "郁保四", " 白胜",
            "时迁", "段景柱"};
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context=".MainActivity" >
    <com.example.parallax.widget.ParallaxListView
        android:id="@+id/plv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

MainActivity 填充数据

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    plv = (ParallaxListView) findViewById(R.id.plv);
    plv.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES));
}

ListView 添加Header

创建header 布局文件

<?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">

    <ImageView
        android:id="@+id/iv_header"
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:contentDescription="@null"
        android:scaleType="centerCrop"
        android:src="@drawable/parallax_img"/>
</RelativeLayout>

第10 行scaleType 为图片的填充模式

图片填充模式对比

  • matrix:图片宽高不变,以ImageView 左上角为基准向右向下填充ImageView
  • fixXY:图片宽高分别拉伸或压缩,以ImageView 左上角为基准向右向下填充满ImageView 的宽和高
  • fitStart:图片宽高分别拉伸或压缩,以ImageView 左上角为基准向右向下填充满ImageView 的宽,ImageView的高不用管
  • fitCenter:图片宽高分别拉伸或压缩,图片居中显示,填充满ImageView 的宽,ImageView 的高不用管
  • fitEnd:图片宽高分别拉伸或压缩,以ImageView 左下角为基准向右向上填充满ImageView 的宽,ImageView的高不用管
  • center:图片宽高不变,图片居中显示,填充ImageView
  • centerCrop:图片宽高分别拉伸或压缩,图片居中显示,直到填充满ImageView
  • centerInside:原图比ImageView 小,图片居中显示,填充ImageView,原图比ImageView 大,图片宽高分别拉伸或压缩,直到填充满ImageView 的宽或高即可

MainActivity.java 中添加头布局

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    plv = (ParallaxListView) findViewById(R.id.plv);
    View headerView = View.inflate(this,R.layout.layout_header, null);
    //添加头布局,需要在setAdapter 前添加
    plv.addHeaderView(headerView);
    plv.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,Cheeses.NAMES));
}

下拉放大

overScrollBy()方法参数解析

ParallaxListView 需要重写overScrollBy()方法,api 要求9 以上

/**
 * 滑动到ListView 两端才会调用
 */
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
                               int scrollY, int scrollRangeX, int scrollRangeY,
                               int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
    //deltaY 竖直方向滑动的瞬时变化量,顶部下拉为-,底部上拉为+
    //scrollY 两端滑动超出的距离,顶部为-,底部为+
    //scrollRangeY 竖立方向滑动的范围
    //maxOverScrollY 竖立方向最大的滑动位置
    //isTouchEvent 是否是用户触摸拉动,true 表示用户手指触摸拉动,false 表示惯性
    System.out.println("deltaY:" + deltaY + " scrollY:" + scrollY
            + " scrollRangeY:" + scrollRangeY + " maxOverScrollY:"
            + maxOverScrollY + " isTouchEvent:" + isTouchEvent);
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
            scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}

 理解deltaY 及isTouchEvent 参数的含义

动态改变头布局的高度

ParallaxListView 添加setParallaxImage()方法

private ImageView headerImage;
public void setParallaxImage(ImageView imageView){
    headerImage = imageView;
}

MainActivity 调用ParallaxListView 的setParallaxImage()方法

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    plv = (ParallaxListView) findViewById(R.id.plv);
    View headerView = View.inflate(this,R.layout.layout_header, null);
    final ImageView headerImage = (ImageView) headerView.findViewById(R.id.iv_header);
    //等view 的树状结构渲染完毕时,再将headerImage 设置到plv 中
    headerImage.getViewTreeObserver().addOnGlobalLayoutListener(newOnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            //宽高已经测量完毕
            plv.setParallaxImage(headerImage);
            //移除监听,避免下次渲染时还调用
            headerImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
    });
    //添加头布局,需要在setAdapter 前添加
    plv.addHeaderView(headerView);
    plv.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, Cheeses.NAMES));
}

第7-18 行view 树状结构渲染完毕时,再将头布局中ImageView 设置到ParallaxListView 中,这样在
ParallaxListView 的setParallaxImage()方法中才能获取到ImageView 的宽高
获取头部ImageView 的高度

private int orignalHeight;
private int drawableHeight;

public void setParallaxImage(ImageView imageView){
    headerImage = imageView;
    //ImageView 初始高度
    orignalHeight = imageView.getHeight();
    //图片原始高度
    drawableHeight = imageView.getDrawable().getIntrinsicHeight();
}

顶部下拉时动态设置头部ImageView 的高度

/**
 * 滑动到ListView 两端才会调用
 */
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
                               int scrollY, int scrollRangeX, int scrollRangeY,
                               int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
    //deltaY 竖直方向滑动的瞬时变化量,顶部下拉为-,底部上拉为+
    //scrollY 两端滑动超出的距离,顶部为-,底部为+
    //scrollRangeY 竖立方向滑动的范围
    //maxOverScrollY 竖立方向最大的滑动位置
    //isTouchEvent 是否是用户触摸拉动,true 表示用户手指触摸拉动,false 表示惯性
    System.out.println("deltaY:" + deltaY + " scrollY:" + scrollY
            + " scrollRangeY:" + scrollRangeY + " maxOverScrollY:"
            + maxOverScrollY + " isTouchEvent:" + isTouchEvent);
    //顶部下拉,用户触摸时,将deltaY 累加给Header
    if(deltaY < 0 && isTouchEvent){
        int newHeight = headerImage.getHeight()+Math.abs(deltaY);
        //新高度小于图片原始高度才允许累加变化量
        if(newHeight <= drawableHeight){
            //让新的值生效
            headerImage.getLayoutParams().height = newHeight;
            headerImage.requestLayout();
        }
    }
    return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
            scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}

第16-24 行动态设置头部ImageView 的高度

回弹动画

ParallaxListView 重写onTouchEvent()方法

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_UP:
            //松手时,把currentHeight 恢复到orignalHeight
            int currentHeight = headerImage.getHeight();
            //300->160 300,299,280,250,200,...160 随时间生成300 到160 间的值
            ValueAnimator animator = ValueAnimator.ofInt(currentHeight,orignalHeight);
            animator.setDuration(500);
            //动画更新的监听
            animator.addUpdateListener(new AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //获取随时间变化得到的currentHeight 到orignalHeight 间的值
                    int value = (Integer) animation.getAnimatedValue();
                    //让新的值生效
                    headerImage.getLayoutParams().height = value;
                    headerImage.requestLayout();
                }
            });
            //设置插值器实现回弹效果
            animator.setInterpolator(new OvershootInterpolator(2));
            animator.start();
            break;
        default:
            break;
    }
    return super.onTouchEvent(ev);
}

第8-23 行手指抬起时,用属性动画实现headerImage 恢复到初始高度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值