PullToZoomScroollView

第一次自己写自定义控件,记录下一些不懂的知识点。

实现一个带头部的ScrollView 下拉头部放大,上滑有时差效果

下面是上代码:

public class PullToZoomScrollView extends ScrollView{
    private  boolean isonce;//加载该View的布局时是否是第一次加载,是第一次就让其实现OnMeasure里的代码

    private LinearLayout mParentView;//布局的父布局,ScrollView内部只能有一个根ViewGroup,就是此View
    private ViewGroup mTopView;//这个是带背景的上半部分的View,下半部分的View用不到的

    private int mScreenHeight;//整个手机屏幕的高度,这是为了初始化该View时设置mTopView用的
    private int mTopViewHeight;//这个就是mTopView的高度

    private int mCurrentOffset=0;//当前右侧滚条顶点的偏移量。ScrollView右侧是有滚动条的,当下拉时,
    //滚动条向上滑,当向下滑动时,滚动条向下滑动。

    private ObjectAnimator oa;//这个是对象动画,这个在本View里很简单,也很独立,就在这里申明一下,后面有两个方法
    //两个方法是:setT(int t),reset()两个方法用到,其他都和它无关了。

    /**
     * 初始化获取高度值,并记录
     * @param context
     * @param attrs
     */
    public PullToZoomScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setOverScrollMode(View.OVER_SCROLL_NEVER);
        WindowManager wm= (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics metrics=new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics); //获取描述该显示的大小和密度的显示指标
        mScreenHeight=metrics.heightPixels;  //用显示器的高度
        mTopViewHeight=mScreenHeight/2-(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, context.getResources().getDisplayMetrics());
    }

    /**
     * 将记录的值设置到控件上,并只让控件设置一次
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(!isonce) {
            mParentView = (LinearLayout) this.getChildAt(0);
            mTopView = (ViewGroup) mParentView.getChildAt(0);
            mTopViewHeight = mTopView.getLayoutParams().height;
            mTopView.getLayoutParams().height = mTopViewHeight;
            isonce=true;
        }
    }

    private float startY=0;//向下拉动要放大,手指向下滑时,点击的第一个点的Y坐标
    private boolean isBig;//是否正在向下拉放大上半部分View
    private boolean isTouchOne;//是否是一次连续的MOVE,默认为false,
    //在MoVe时,如果发现滑动标签位移量为0,则获取此时的Y坐标,作为起始坐标,然后置为true,为了在连续的Move中只获取一次起始坐标
    //当Up弹起时,一次触摸移动完成,将isTouchOne置为false
    private float distance=0;//向下滑动到释放的高度差
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action =ev.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                if(mCurrentOffset<=0){
                    if(!isTouchOne){
                        startY=ev.getY();
                        isTouchOne=true;
                    }
                    distance=ev.getY()-startY;
                    if(distance>0){
                        isBig=true;
                        setT((int)-distance/4);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if(isBig) {
                    reset();
                    isBig=false;
                }
                isTouchOne=false;
            break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 对象动画要有的设置方法
     * @param t
     */
    public void setT(int t) {
        scrollTo(0, 0);
        if (t < 0) {
            mTopView.getLayoutParams().height = mTopViewHeight-t;
            mTopView.requestLayout();
        }
    }

    /**
     * 主要用于释放手指后的回弹效果
     */
    private void reset() {
        if (oa != null && oa.isRunning()) {
            return;
        }
        oa = ObjectAnimator.ofInt(this, "t", (int)-distance / 4, 0);
        oa.setDuration(150);
        oa.start();
    }

    /**
     * 这个是设置向上滑动时,上半部分View滑动速度让其小于下半部分
     * @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);
        mCurrentOffset = t;//右边滑动标签相对于顶端的偏移量
        //当手势上滑,则右侧滚动条下滑,下滑的高度小于TopView的高度,则让TopView的上滑速度小于DownView的上滑速度
        //DownView的上滑速度是滚动条的速度,也就是滚动的距离是右侧滚动条的距离
        //则TopView的速度要小,只需要将右侧滚动条的偏移量也就是t缩小一定倍数就行了。我这里除以2速度减小1倍
        if (t <= mTopViewHeight&&t>=0&&!isBig) {
            mTopView.setTranslationY(t / 2);//使得TopView滑动的速度小于滚轮滚动的速度
        }
        if(isBig){
            scrollTo(0,0);
        }

    }
}

下面记录流程:
1.创建PullToZoomScrollView 继承 scrollView
这里只提供了一个构造方法必须在在xml文件中创建。
构造方法中做了几件事:设置ScrollView模式 this.setOverScrollMode(View.OVER_SCROLL_NEVER); 去除下拉时阴影
计算TopView的高度:获取WindowManager 从windowManager中拿到 metrics 屏幕数据参数,

TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, context.getResources().getDisplayMetrics());

这里解释下这个函数:将包含维度的解压缩复合数据值转换为其最终浮点值。
三个参数分别为:
unit:转换单位。
value: 应用单位的价值。
metrics:在转换中使用的当前显示指标 - 提供显示密度和缩放信息。

2.onMeasure()中,当第一次执行时,拿到mParentView 、mTopView 、并设置mTopViewHeight.

  1. onTouchEvent(MotionEvent ev)
    MotionEvent.ACTION_MOVE,在顶部且下滑时,记录下第一次滑动的点startY
    通过之后的触摸点计算出滑动距离distance,并设置mTopView的高度。
    这里记录下 requestLayout 与 invalidate的区别:

requestLayout:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。
特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。

invalidate:该方法的调用会引起View树的重绘,常用于内部调用(比如 setVisiblity())或者需要刷新界面的时候,需要在主线程(即UI线程)中调用该方法。一般只会调用onDraw重新绘制。

MotionEvent.ACTION_UP,在滑动结束后如果动画还未结束则调用reset()方法
执行动画

  oa = ObjectAnimator.ofInt(this, "t", (int)-distance / 4, 0);

4.onScrollChanged
在滑动时记录偏移量 mCurrentOffset
这里产生头部时差效果 将mTopView的滑动速度小于滚轮滑动的速度

   if (t <= mTopViewHeight&&t>=0&&!isBig) {
            mTopView.setTranslationY(t / 2);//使得TopView滑动的速度小于滚轮滚动的速度
        }

如果下拉放大时,执行scrollTo(0,0).

第一次写技术文章,写的不好见谅

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,070评论 25 707
  • 本文分析版本: Android API 23 1.简介 ScrollView是我们在开发中经常使用的控件。当我们需...
    SkyKai阅读 8,921评论 3 54
  • 3.4 View详解 3.10.1 概述 View系统定义了从用户输入消息到消息处理的全过程:用户通过触摸屏或者键...
    jianhuih阅读 605评论 0 0
  • 2017.11.29号 星期 三 天气 晴转阴 今天晚上接着孩子后我们一起去超市买了些东西去妈妈家吃火锅,...
    吕政民阅读 139评论 0 0
  • 01。 三年前的夏天,我和女朋友在省医院后面的老校区里租了套二的房间,一来上班更近了,二来房租便宜。 小区和送仙桥...
    大山里的阿木阅读 813评论 1 8