android之用scrollview实现控件滑动固定效果

项目中最近用到需要布局滑动到某一个地方的时候某个控件固定在屏幕顶部不动,就去研究了下,思路其实挺简单的。我置顶的悬浮控件上边还需要留个控件,比如搜索框之类的,项目需求不一样就留的不一样,所以就研究了一下,网上也有很多,其实方法思路都一样的,很简单,自定义一下ScrollView就可以了。

借鉴来自:点击打开链接

然后自己就写了下,效果图如下:


哎哟,废话说多了心口疼,不废话了直接开始吧,首先我们每次做一个功能前需要分析怎么实现,我们要的步骤:

(1)需要知道这个scrollview滑动了多少,因为我们是根据滑动距离来判断是否让绿色那个框停在当前页的上面,那么我们需要滑动多少才让他停下来呢?看下图:

我们需要:手指上滑时当绿色框的顶部从位置3滑动到位置2时就停靠在位置2那里了;下滑时绿色框的顶部到达位置2的时候停靠的绿色框不停靠

(2)怎么停靠?我们可以让整个界面布局使用relativelayout,让搜索那个框在最上面,然后需要停靠的绿色框隐藏在搜索框下面,简而言之就是2个绿色框了,一个是在scrollview里面即上图中看见的绿色框(简称内部绿色框),一个是在搜索栏下面隐藏着的(简称外部绿色框),当我们此时上滑时,内部绿色框从位置3到位置2我们就显示外部绿色框;反之隐藏

(3)获取位置3到位置2的距离distance,然后与scrollview滑动的距离scrollY进行比较,如果scrllY大于或等于distance,那么就说明我们从位置3滑动到了位置2或者位置2的上面,那么我们就需要显示外部绿色框,如果小于说明还没到达位置2,那么我们就不显示外部绿色框

好了,说了这么多应该能听懂吧,你要是听不懂。。。。。。。。好吧,怪我咯,说不清楚,我的错。下面那就直接开始代码吧

首先自定义MyScrollView获取滑动的距离利用scrollview的getscrollY方法获取滑动的距离,那有人就问了,既然scrollview有这个方法,为什么要重写呢,直接在外面用scrollview.getScrollY()不就行了。那么。。。可以是可以,但是大锅,我们需要随时获取他的高度,而不是我们执行一次getScrollY就获取一次,我们需要在滑动过程中随时随地获取他的高度来进行比较,不然你在外面用scrollview.getScrollY()方法,怎么写?用handler每x毫秒获取一次?那也许恰好在你2次获取的间隔中我到达了位置2呢,而且你的handler什么时候开始执行?什么时候不执行,还有如果我瞬滑(别问我瞬滑是谁,你问静静去)呢,那我手指离开了但是scrollview还在滑动怎么办。。。不废话了,懂了吧,所以我们需要在scrollview的onTouchEvent方法中监听他的touch事件,只要你要滑动,那你肯定要touch这个scrollview,所以我们在他的onTouchEvent方法中获取滑动距离是最好的,只要onTouchEvent方法被触发那就执行getscrollY(),还要加一个特殊情况,如果手指离开了,即ACTION_UP,那么我们需要隔一会儿就获取一下距离,然后跟手指离开时的距离进行比较,如果不一样,那就说明手指放开后还滑动了,那就再更新scrollY的值,把scrollY的值通过自己写的interface设置listener传给activity就行了,思路说完了,代码直接贴上来了:

package com.custom.my.view;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class MyScrollView extends ScrollView {

    private  MyScrollListener listener;
    private int scrollDistanceY;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == 1){
                int newY = getScrollY();  //获取手指离开后再次更新获取的Y值
                if(newY != scrollDistanceY){ //如果不等与手指离开后的Y值,说明滑动还没停止,继续获取新值
                    scrollDistanceY = newY;  //将新值赋给参数
                    listener.sendDistanceY(scrollDistanceY);  //传递新值
                    handler.sendMessageDelayed(handler.obtainMessage(), 5);  //滑动没停止,再次获取
                }
            }
        }
    };

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

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

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

    public interface MyScrollListener{  //接口,用于传递滑动的Y值数据,activity实现该接口即可获取该值
        void sendDistanceY(int distance);
    }

    public void setMyScrollListener(MyScrollListener myScrollListener){  //绑定
        this.listener = myScrollListener;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {  //事件处理
        if(ev.getAction() == MotionEvent.ACTION_UP){  //如果手指离开,就30毫秒后再次请求,其他操作不影响,都传递滑动距离即可
            handler.sendEmptyMessageDelayed(1,30);  //30毫秒后再次执行,以防手指离开后还在继续滑动
        }
        scrollDistanceY = getScrollY();   //获取滑动的Y值
        listener.sendDistanceY(scrollDistanceY);//传递Y值
        return super.onTouchEvent(ev);
    }
}

上面自定义的MyScrollview,该说明的都说了,下面是布局文件:

<?xml version="1.0" encoding="utf-8"?>
<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="com.custom.my.activity.ScrollTest">

    <com.custom.my.view.MyScrollView
        android:id="@+id/scv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:id="@+id/img"
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:scaleType="centerCrop"
                android:src="@drawable/ic_mine_head_background"/>
            <TextView
                android:id="@+id/move"
                android:layout_width="match_parent"
                android:layout_height="35dp"
                android:gravity="center"
                android:background="#00ff00"
                android:text="老子就是要动,不服打我"/>
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:scaleType="centerCrop"
                android:src="@drawable/ic_mine_head_background"/>
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:scaleType="centerCrop"
                android:src="@drawable/ic_mine_head_background"/>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:gravity="center"
                android:background="#50ff0000"
                android:text="图片浏览"/>
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:scaleType="centerCrop"
                android:src="@drawable/ic_mine_head_background"/>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:gravity="center"
                android:background="#50ff0000"
                android:text="图片浏览"/>
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:scaleType="centerCrop"
                android:src="@drawable/ic_mine_head_background"/>
        </LinearLayout>
    </com.custom.my.view.MyScrollView>


    <TextView
        android:id="@+id/head"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:background="#000000"
        android:textColor="#ffffff"
        android:text="打死我也不会动的,我是搜索哥(搜索按钮)"/>
    <TextView
        android:id="@+id/stop"
        android:visibility="gone"
        android:layout_below="@+id/head"
        android:layout_width="match_parent"
        android:layout_height="35dp"
        android:gravity="center"
        android:background="#00ff00"
        android:text="老子就是要动,不服打我"/>

</RelativeLayout>

布局是基础,就不多废话了,你要我多废话我也不多,口水不够啊。

接下来就是activity的操作了:

head表示头部搜索栏,stop表示外面绿色栏即需要停靠的那个,move表示在scrollview里面需要跟着滑动的内部绿色栏,myScrollView表示自定义的MyscrollView

那么,当前activity implements MyscrollView.MyScrollListener,实现该接口获取滑动的Y值

myScrollView.setMyScrollListener(this);  //绑定

接下来需要获取位置2和位置3之间的距离,如何获取呢?很简单,获取位置3的到顶部的距离减去位置2到顶部的距离,那么

我们需要重写activity的 onWindowFocusChanged方法,在该方法中获取位置3和位置2:

@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus){
            topDistance = move.getTop();  //获取位置3,即内部绿色栏的顶部到布局顶部的距离
            height = head.getMeasuredHeight();  //获取位置2,不就是搜索栏的高度么,啊哈哈哈,是不是很机智,当然你也可以用getButtom,一样的,看你自己
        }
    }

接下来实现了MyscrollView.MyScrollListener这个,我们可以得到滑动的距离了:

@Override
    public void sendDistanceY(int scrollY) {
        Log.d("scroll","----------------------height:"+height);
        if(scrollY >= topDistance - height){  //如果滑动的距离大于或等于位置3到位置2的距离,那么说明内部绿色的顶部在位置2上面了,我们需要显示外部绿色栏了
            stop.setVisibility(View.VISIBLE);
        }else {  //反之隐藏
            stop.setVisibility(View.GONE);
        }
    }

好了,说完了,看着废话多,其实很简单,思路很简单,不过怕新手看不懂,废话写得有点多了,而且初发博客,不大有经验,大家别喷,在下赵日天谢过了。

补充1:有人可能会问为什么要用一个外部和一个内部呢,用一个不行么?答案是:当然可以,但是你如果用一个的话,当你外部绿色按钮显示的时候你的内部就删除了,这样你的整个layout的高度就变了,你试试会发现布局突然变矮了一截,看着很不流畅(外部显示的时候,就addview到外部,内部就用removeview删除),看你自己了,当然还有其他方法,比如用一个空的view占高之类的,反正你自己看着办咯,我也管不了咯,你爱怎么样就怎么样咯

补充2:获取高度啊,到顶部距离啊之类的,记住在onWindowFocusChanged方法中获取,如果在oncreate中获取永远是0,因为还没绘制完成,当然你也可以在onGlobalLayout中获取,看你自己了

注意:

if(ev.getAction() == MotionEvent.ACTION_UP){  //如果手指离开,隔30就再执行请求获取Y值看是否跟手指离开时的Y值一致
            handler.sendEmptyMessageDelayed(1,30);
        }
在该方法中handler.sendEmptyMessageDelayed(1,30);这个30都懂吧,意思是延迟操作,尽量别写太小比如1-10之内的,为什么呢,因为这个是手指离开后30毫秒再次获取高度的,如果时间太短,比如1毫秒,也许恰好1毫秒后还在继续滑动,而获取的高度和手指离开时的高度一样,这样就会停止更新获取,导致数据不准确,总而言之就是,间隔太短导致数据超短时间内一致,数据就不准确了,当然也不要太长了,为啥就不用我讲了吧。好累,我得回去补补肾了



最后最后。。。。。。特么的基本不用博客,写这个改了我好几遍才正常,都是泪





发布了33 篇原创文章 · 获赞 49 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/gsw333/article/details/50634463