Android自定义ScrollView:实现滑动顶部停靠

先看看实现效果:

自定义ScrollView实现滑动顶部停靠的过程:

第一步 首先自定义MyScrollview 继承Scrollview,实现滑动的监听

在onTouchEvent()方法中监听用户的触摸操作,并在onScroll方法中获取ScrollView滑动的距离。
详细代码如下:

/*
 * ScrollView并没有实现滚动监听,我们必须自行实现对ScrollView的监听,
 * 我们很自然的想到在onTouchEvent()方法中实现对滚动Y轴进行监听;
 * ScrollView的滚动Y值进行监听;
 */
public class MyScrollView extends ScrollView {
    private OnScrollListener onScrollListener;
    /**
     * 主要是用在用户手指离开MyScrollView,MyScrollView还在继续滑动,我们用来保存Y的距离,然后做比较
     */
    private int lastScrollY;

    public MyScrollView(Context context) {
        super(context, null);
    }
    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
    }
    public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    /**
     * 设置滚动接口
     * @param onScrollListener
     */
    public void setOnScrollListener(OnScrollListener onScrollListener){
        this.onScrollListener = onScrollListener;
    }
    /**
     * 用于用户手指离开MyScrollView的时候获取MyScrollView滚动的Y距离,然后回调给onScroll方法?
     */
    private Handler handler = new Handler() {

        public void handleMessage(android.os.Message msg) {
            int scrollY = MyScrollView.this.getScrollY();

            //此时的距离和记录下的距离不相等,在隔5毫秒给handler发消息
            if(lastScrollY != scrollY){
                lastScrollY = scrollY;
                handler.sendMessageDelayed(handler.obtainMessage(), 5);
            }
            if(onScrollListener != null){
                onScrollListener.onScroll(scrollY);
            }

        };

    };
    /**
     * 重写onTouchEvent 当用户的手在MyScrollView上面的时候,
     * 直接将MyScrollView滑动的Y方向距离回调给onScroll方法中,当用户抬起手的时候,
     * MyScrollView可能还在滑动,所以当用户抬起手我们隔5毫秒给handler发消息,在handler处理
     * MyScrollView滑动的距离
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(onScrollListener != null){
            onScrollListener.onScroll(lastScrollY = this.getScrollY());
        }
        switch(ev.getAction()){
            case MotionEvent.ACTION_UP:              handler.sendMessageDelayed(handler.obtainMessage(), 5);
                break;
        }
        return super.onTouchEvent(ev);
    }


    /*@Override
    protected void dispatchDraw(Canvas canvas) {
        if(onScrollListener != null){
            onScrollListener.onScroll(getScrollY());
        }
        super.dispatchDraw(canvas);
    }*/

    /**
     * 滚动的回调接口
     */
    public interface OnScrollListener{
        /**
         * 回调方法:返回MyScrollView滑动的Y方向距离
         */
        public void onScroll(int scrollY);
    }

第二步 布局的详细变化过程:

如下图,在布局文件中,我写了两个高度一样的线性布局,分别是顶部的蓝色布局search01,和在MyScrollView中的绿色布局search02,在开始时,search01中为空,search02中包含我们的content布局(就是实现停靠的那部分View)。当search02向上滑动并与search01布局重合时,执行search02.remove(content) 将content从search02中移除, search01.add(content) 把content添加到search01中,从而在视觉上看起来像是content固定在了顶部。

Android自定义ScrollView:实现滑动顶部停靠_第1张图片

详细代码如下:

public class MainActivity extends Activity implements MyScrollView.OnScrollListener {

    private MyScrollView myScrollView;
    private int searchLayoutTop;

    LinearLayout search01,search02;//两个高度一样的布局
    LinearLayout rlayout;
    LinearLayout ll_call;//content布局中的打电话View
    private RelativeLayout rl_lianxiren;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        init();
    }

    private void init() {
        myScrollView = (MyScrollView)findViewById(R.id.myScrollView);
        search01 = (LinearLayout)findViewById(R.id.search01);
        search02 = (LinearLayout)findViewById(R.id.search02);
        rlayout = (LinearLayout)findViewById(R.id.rlayout);
        rl_lianxiren = (RelativeLayout)findViewById(R.id.rl_lianxiren);//这就是我们的content布局,即需要停靠的View
        ll_call=(LinearLayout)findViewById(R.id.ll_call);
        myScrollView.setOnScrollListener(this);
        ll_call.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this,"预定电话",Toast.LENGTH_SHORT).show();
            }
        });

    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus){
            searchLayoutTop = rlayout.getBottom();//获取searchLayout的顶部位置
        }
    }

    //监听滚动Y值变化,通过addView和removeView来实现悬停效果
    @Override
    public void onScroll(int scrollY) {
        if(scrollY >= searchLayoutTop){//当总布局滑动到拨打电话布局的顶部时;
            if (rl_lianxiren.getParent()!=search01) {
                search02.removeView(rl_lianxiren);//将content布局移除search02布局,并添加到search01中
                search01.addView(rl_lianxiren);
            }
        }else{
            if (rl_lianxiren.getParent()!=search02) {
                search01.removeView(rl_lianxiren);
                search02.addView(rl_lianxiren);
            }
        }
    }
}

第三步 什么时候判断search01布局和search02布局重合?

核心代码是:

   searchLayoutTop = rlayout.getBottom();//获取searchLayout的bottom位置;
   .....
  if(scrollY >= searchLayoutTop){//当总布局滑动到content布局的顶部时,即两个布局重合的时候;
  .....

首先需要要介绍一下Android View坐标getLeft, getRight, getTop, getBottom的含义,这里涉及坐标系的概念:
坐标系在二维视图中通过X轴和Y轴两个数字为组合表示某个点的绝对坐标。例如(20, 120) 通常表示X轴20, Y轴120交叉的一个点

在Android中可以把left相当于X轴值, top相当于Y轴值, 通过这两个值Android系统可以知道视图的绘制起点,在通过Wdith 和 Height 可以得到视图上下左右具体值,就可以在屏幕上绝对位置绘制视图。right 与 bottom计算如下:

right = left + width;

bottom = top + height;

借用主席的一张图来说明View的坐标系原理:
Android自定义ScrollView:实现滑动顶部停靠_第2张图片

完成上面三步就可以实现任意布局的顶部停靠了,当然这只是实现停靠的一种方式,还有一种方式是可以开始的时候有两个一模一样的绿色布局,滑动的时候一起往上滑动,当判断滑动到顶部时,一个滑出,一个停靠,这也能实现View的顶部停靠。

完整代码下载

你可能感兴趣的:(Android自定义View)