前言

ViewPager是我们常用的控件之一,此篇文章我们用Scroller等知识实现一个简单的ViewPager。

效果:

(源码在文章结尾)

用Scroller完成一个简单的ViewPager_第1张图片

涉及知识点

onMeasure和onLayout

此点若不了解可以参考郭霖前辈的文章:
Android视图绘制流程完全解析,带你一步步深入了解View(二)

事件分发机制

此点可以参考笔者文章:
完全理解android事件分发机制

scrollTo和scrollBy

scrollTo:以View的初始位置为起点进行移动
scrollBy:以View的当前位置为起点进行移动

TouchSlop

系统可以识别出的被认为是滑动的最小距离。如果大于这个距离则是滑动。

View.getScrollX()

getScrollX()获取到的值是屏幕的最左侧在整个空间中所占位置的X值。
打个比方:View是一条6米的绳子,而屏幕只能看到2~4米的绳子。那么getScrollX()的值就为2。如果屏幕看到的是3~5米的绳子,那么getScrollX()的值就为3。

Scroller

Scroller的使用主要为3步:
1、初始化Scroller
2、重写computeScroll()方法
computeScroll()是在View的draw的时候调用的,而invalidate会导致View重绘,所以在重写computeScroll()之后,我们要使用invalidate()来间接调用它。
过程为:invalidate()->draw()->computeScroll()
3、使用startScroll()开启滑动

代码:

MainActivity:

package com.example.double2.scrollertest;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends AppCompatActivity implements View.OnClickListener {    private Button btnOne;    private Button btnTwo;    private Button btnThree;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btnOne=(Button)findViewById(R.id.btn_one);        btnTwo=(Button)findViewById(R.id.btn_two);        btnThree=(Button)findViewById(R.id.btn_three);        btnOne.setOnClickListener(this);        btnTwo.setOnClickListener(this);        btnThree.setOnClickListener(this);    }    @Override    public void onClick(View v) {        switch (v.getId()){            case R.id.btn_one:                Toast.makeText(this, "One", Toast.LENGTH_SHORT).show();                break;            case R.id.btn_two:                Toast.makeText(this, "Two", Toast.LENGTH_SHORT).show();                break;            case R.id.btn_three:                Toast.makeText(this, "Three", Toast.LENGTH_SHORT).show();                break;        }    }}

ScrollerLayout:

package com.example.double2.scrollertest;import android.content.Context;import android.support.v4.view.ViewConfigurationCompat;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.widget.Scroller;/** * 项目名称:ScrollerTest * 创建人:Double2号 * 创建时间:2017.2.23 12:02 * 修改备注: */public class ScrollerLayout extends ViewGroup {    private Scroller mScroller;    //用于判断是否是滑动    private int mTouchSlop;    //按下时的X坐标    private float downX;    //滑动时的X坐标    private float moveX;    //上次触发ACTION_MOVE事件时的屏幕坐标    private float lastMoveX;    //界面可滚动的左边界    private int leftBorder;    //界面可滚动的右边界    private int rightBorder;    public ScrollerLayout(Context context, AttributeSet attrs) {        super(context, attrs);        // 创建Scroller的实例        mScroller = new Scroller(context);        ViewConfiguration configuration = ViewConfiguration.get(context);        // 获取TouchSlop值        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int childCount = getChildCount();        for (int i = 0; i < childCount; i++) {            View childView = getChildAt(i);            // 为ScrollerLayout中的每一个子控件测量大小            measureChild(childView, widthMeasureSpec, heightMeasureSpec);        }    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        if (changed) {            int childCount = getChildCount();            for (int i = 0; i < childCount; i++) {                View childView = getChildAt(i);                // 为ScrollerLayout中的每一个子控件在水平方向上进行布局                childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());            }            // 初始化左右边界值            leftBorder = getChildAt(0).getLeft();            rightBorder = getChildAt(getChildCount() - 1).getRight();        }    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                downX = ev.getRawX();                lastMoveX = downX;                break;            case MotionEvent.ACTION_MOVE:                moveX = ev.getRawX();                float diff = Math.abs(moveX - downX);                lastMoveX = moveX;                // 当手指拖动值大于TouchSlop值时,认为应该进行滑动,不进行点击事件                if (diff > mTouchSlop) {                    return true;                }                break;        }        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                return true;            case MotionEvent.ACTION_MOVE:                moveX = event.getRawX();                int scrolledX = (int) (lastMoveX - moveX);                if (getScrollX() + scrolledX < leftBorder) {                    scrollTo(leftBorder, 0);                    return true;                } else if (getScrollX() + getWidth() + scrolledX > rightBorder) {                    scrollTo(rightBorder - getWidth(), 0);                    return true;                }                scrollBy(scrolledX, 0);                lastMoveX = moveX;                break;            case MotionEvent.ACTION_UP:                // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面                //如果当前X超过一半的宽度,那么就向左移                //如果当前的X小鱼一半的宽度就向右移                int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();                int dx = targetIndex * getWidth() - getScrollX();                mScroller.startScroll(getScrollX(), 0, dx, 0);// 调用startScroll()方法来初始化滚动数据并刷新界面                invalidate();//通过invalidate()间接调用computeScroll()                break;        }        return super.onTouchEvent(event);    }    @Override    public void computeScroll() {        // computeScroll()是不会自己调用,只能通过invalidate()->draw()->computeScroll()来间接调用        //computeScrollOffset()用来判断是否完成了整个华东        if (mScroller.computeScrollOffset()) {            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());            invalidate();        }    }}

activity_main:

<?xml version="1.0" encoding="utf-8"?><com.example.double2.scrollertest.ScrollerLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <Button        android:id="@+id/btn_one"        android:layout_width="match_parent"        android:layout_height="100dp"        android:text="One"/>    <Button        android:id="@+id/btn_two"        android:layout_width="match_parent"        android:layout_height="100dp"        android:text="Two"/>    <Button        android:id="@+id/btn_three"        android:layout_width="match_parent"        android:layout_height="100dp"        android:text="Three"/>com.example.double2.scrollertest.ScrollerLayout>

源码地址:

http://download.csdn.net/detail/double2hao/9762018

更多相关文章

  1. android 事件总线 -- Otto(一)
  2. Android.GridView事件监听
  3. design principle:模拟 android Button 控件点击事件
  4. Android 总结:打造Android中的流式布局和热门标签(源码有详细注释)
  5. Android源码笔记--Activity的启动过程
  6. 10个经典的Android开源项目(附源码包)
  7. 【Android系统源码修改】在系统设置中添加设置选项

随机推荐

  1. Announcing the 2016 Android Experiment
  2. android 利用socket 发送Json数据demo
  3. XML中转义字符(Android可用)
  4. android获取短信方法1
  5. android unit test
  6. Android 将TabHost放在最下方显示
  7. 获取Android 唯一标识
  8. android 开机启动服务
  9. android studio 生成 release aar
  10. Android(安卓)跨应用调用Activity及Servi