自定义View-平滑滚动
来源:互联网 发布:php 制作扇形统计图 编辑:程序博客网 时间:2024/04/29 20:59
TouchSlidingScreen:
public class TouchSlidingScreen extends ViewGroup { //平滑滚动中要用到Scroller private Scroller scroller; //最小滑动距离,超过了才认为开始滑动 private int touchSlop = 0; //停止状态 private static final int TOUCH_STATE_STOP = 0; //滑动状态 private static final int TOUCH_STATE_FLING = 1; private int touchState = TOUCH_STATE_STOP; ///上次触摸屏的 x 位置 private float lastX = 0; //当前位置 private int curScreen; //VelocityTracker 主要用于跟踪触摸屏事件(flinging 事件和其他 gestures 手势事件)的速率 private VelocityTracker velocityTracker; public static int SNAP_VELOCITY = 600; //最小的滑动速率 public TouchSlidingScreen(Context context) { this(context, null); } public TouchSlidingScreen(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TouchSlidingScreen(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public void init(Context context) { scroller = new Scroller(context); //获取到当前手机上默认的最小滑动距离 touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //测量子View measureChildren(widthMeasureSpec, heightMeasureSpec); int width = measureWidth(widthMeasureSpec); int height = measureHeight(heightMeasureSpec); //保存测量自己的宽高 setMeasuredDimension(width, height); } //子View宽必须是match_parent,容器总的宽是一屏*子View的个数 private int measureWidth(int widthMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int width = 0; //ViewGroup不能是包裹 if (widthMode == MeasureSpec.AT_MOST) { throw new IllegalStateException("the width must is match_parent!"); } else { width = widthSize; } return width * getChildCount(); } //容易的高必须是match_parent private int measureHeight(int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int height = 0; if (heightMode == MeasureSpec.AT_MOST) { throw new IllegalStateException("the height must is match_parent!"); } else { height = heightSize; } return height; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); int childWidth = (r - l) / count;//一屏的宽度 int childHeight = b - t;//一屏的高度 for (int i = 0; i < count; i++) { int left = i * childWidth; int top = 0; int right = (i + 1) * childWidth; int bottom = childHeight; View childView = getChildAt(i); //定位子View childView.layout(left, top, right, bottom); } } //事件拦截 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); int x = (int) ev.getX(); int y = (int) ev.getY(); if (action == MotionEvent.ACTION_MOVE && touchState == TOUCH_STATE_STOP) {//滑动并且屏幕中的状态是停止时 return true; } switch (action) { case MotionEvent.ACTION_DOWN: lastX = x; touchState = scroller.isFinished() ? TOUCH_STATE_STOP : TOUCH_STATE_FLING; break; case MotionEvent.ACTION_HOVER_MOVE: //滑动距离过小不算滑动 int dx = (int) Math.abs(x - lastX); if (dx > touchSlop) { touchState = TOUCH_STATE_FLING; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: touchState = TOUCH_STATE_STOP; break; } return touchState != TOUCH_STATE_STOP;//滑动时返回true拦截,不交给子View } /** * @param event * @return */ public boolean onTouchEvent(MotionEvent event) { if (velocityTracker == null) { //使用 obtain()方法得到这个类的实例 velocityTracker = VelocityTracker.obtain(); } //addMovement(MotionEvent)函数将你接受到的 motionevent 加入到 VelocityTracker 类实例中 velocityTracker.addMovement(event); super.onTouchEvent(event); int action = event.getAction(); final int x = (int) event.getX(); switch (action) { case MotionEvent.ACTION_DOWN: //手指按下时,如果正在滚动,则立刻停止 if (scroller != null && !scroller.isFinished()) { scroller.abortAnimation(); } lastX = x; break; case MotionEvent.ACTION_MOVE: //随手指滚动 int dx = (int) (lastX - x); scrollBy(dx, 0); lastX = x; break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = this.velocityTracker; //使用 computeCurrentVelocity(int)初始化速率的单位 velocityTracker.computeCurrentVelocity(1000); //获得x方向的速率 int velocityX = (int) velocityTracker.getXVelocity(); //通过 velocityX 的正负值可以判断滑动方向 if (velocityX > SNAP_VELOCITY && curScreen > 0) { moveToPrevious(); } else if (velocityX < -SNAP_VELOCITY && curScreen < (getChildCount() - 1)) { moveToNext(); } else { moveToDestination(); } if (velocityTracker != null) {//释放并回收资源 this.velocityTracker.clear(); this.velocityTracker.recycle(); this.velocityTracker = null; } touchState = TOUCH_STATE_STOP; break; case MotionEvent.ACTION_CANCEL: touchState = TOUCH_STATE_STOP; break; } return true; } public void moveToScreen(int whichScreen) { curScreen = whichScreen; int scrollX = getScrollX(); int splitWidth = getWidth() / getChildCount(); int dx = curScreen * splitWidth - scrollX; scroller.startScroll(scrollX, 0, dx, 0, Math.abs(dx)); invalidate(); } @Override public void computeScroll() { super.computeScroll(); if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } } public void moveToDestination() { //每一屏的宽度 int splitWidth = getWidth() / getChildCount(); //判断是回滚还是进入下一分屏 int toScreen = (getScrollX() + splitWidth / 2) / splitWidth; //移动到目标分屏 moveToScreen(toScreen); } /** * 滚动到下一屏 */ public void moveToNext() { if (curScreen >= getChildCount() - 1) { Toast.makeText(getContext(), "当前已经是最后一页", Toast.LENGTH_SHORT).show(); return; } moveToScreen(curScreen + 1); } /** * 滚动到上一屏 */ public void moveToPrevious() { if (curScreen <= 0) { Toast.makeText(getContext(), "当前已经是第一页", Toast.LENGTH_SHORT).show(); return; } moveToScreen(curScreen - 1); }}
activity_main:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.example.user.myapplication4.TouchSlidingScreen android:id="@+id/ml" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <LinearLayout android:layout_width="0dp" android:layout_height="0dp" android:background="#FFFF00" android:orientation="vertical"></LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="0dp" android:background="#00FF00" android:orientation="vertical"></LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="0dp" android:background="#0000FF" android:orientation="vertical"></LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="0dp" android:background="#00FFFF" android:orientation="vertical"></LinearLayout> </com.example.user.myapplication4.TouchSlidingScreen> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/pre" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="上一屏" /> <Button android:id="@+id/next" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_green_light" android:text="下一屏" /> </LinearLayout></LinearLayout>
MainActivity:
public class MainActivity extends Activity { private TouchSlidingScreen ml; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ml = (TouchSlidingScreen) findViewById(R.id.ml); Button pre = (Button) findViewById(R.id.pre); Button next = (Button) findViewById(R.id.next); pre.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ml.moveToPrevious(); } }); next.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ml.moveToNext(); } }); Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { handler.sendEmptyMessage(0); } },2000,3000); } Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case 0: ml.moveToNext(); break; } } };}
第一步:初始化。平滑滚动需要使用 Scroller 对象,另外还需要给定一个最小滑动距离,通过 ViewConfiguration.get(context).getScaledTouchSlop()可以获取到当前手机上默认的最小滑动距离。
第二步:测量容器宽度与高度。不允许使用 MeasureSpec.AT_MOST,每个子组件与容器相同,容器的 layout_width 值虽然为 MeasureSpec.EXACTLY,但容器大小 =父容器的宽度 *子组件的个数,高度与父容器相同。
第三步:定位子组件。默认情况下,屏幕出现第一个子组件,子组件占满容器的可见区域,其他子组件以相同大小依次排列在后面。
第四步:判断滚动状态,状态为分两种:停止状态和滑动状态。容器根据状态决定是否截拦事件。
第五步:惯性滚屏:
手指滑动距离如果超过容器一半或者滑动速度足够快,则进入下一屏(或者上一屏)。如果没有超过一半或速度很慢则回滚到初始位置。定义 moveToDestination()方法如下,最关键的语句是 inttoScreen=(getScrollX()+splitWidth/2 ) / splitWidth,getScrollX()是容器滚动过的距离,splitWidth 是每一屏的宽度。比如每一屏的宽度为 10,当前屏为第 2 屏,容器已滚过 23,则 toScreen=(23 +10/ 2) / 10=(23 + 5) / 10 = 28/10 = 2.8= 2,也就是说要回滚到第 2 屏;如果容器已滚动28,则 toScreen=(28 + 10 /2)/ 10 = 32/10 = 3.2 =3,表示要滚动到第 3 屏。
第六步:响应用户手指的按下、移动和松开事件,这是整个滑动的关键,特别是松开后,要判断滚屏还是回滚。为了支持上一屏和下一屏,需要辨别手指滑动的方向,VelocityTracker 类可以获取 x 方向的速率,其正值代表向左滑动,负值代表向右滑动。如果 x 方向的速率在[-SNAP_VELOCITY,SNAP_VELOCITY]之间,则要根据用户滑动的距离(滑动距离是否超过一屏的1/2)决定是要继续滚屏还是回滚到初始状态。
VelocityTracker :
addMovement(MotionEvent)函数将你接受到的 motion event 加入到 VelocityTracker 类实例中。当我们需要使用到速率时,使用 computeCurrentVelocity(int)初始化速率的单位,并获得当前的事件的速率,然后使用 getXVelocity()或 getXVelocity()获得横向和竖向的速率。另外,通过VelocityTracker 还可以知道手指的滑动方向。
VelocityTracker 的基本使用如下:
手指按下时(ACTION_DOWN),获取 VelocityTracker 对象
if(velocityTracker==null){
//创建 velocityTracker 对象
velocityTracker=VelocityTracker.obtain();
}
//关联事件对象
velocityTracker.addMovement(ev);
手指移动过程中(ACTION_MOVE),计算速率
velocityTracker.computeCurrentVelocity(1000);
获取 x、y 两个方向的速率:
int velocityX= velocityTracker.getXVelocity();
int velocityY =velocityTracker.getYVelocity();
手指松开后(ACTION_UP),释放并回收资源
//释放 VelocityTracker 资源
if(velocityTracker !=null){
velocityTracker.clear();
velocityTracker.recycle();
velocityTracker=null;
}
- 自定义View-平滑滚动
- 自定义view滚动列表
- 自定义View 滚动条
- 平滑滚动
- 自定义View添加滚动条
- 178_自定义滚动View
- jQuery写出可调控自定义的平滑滚动效果
- Android 自定义View制作随时间增长的平滑进度条
- 让自定义的view滚动起来
- 自定义控件view,并且使用滚动条
- 让自定义的view滚动起来
- android自定义View-垂直滚动的TextView
- android自定义View-垂直滚动的TextView
- android自定义View-垂直滚动的TextView
- 自定义view---滚动的刻度尺(一)
- 自定义view-----滚动的刻度尺(二)
- 自定义view---滚动的刻度尺(三)
- 自定义view---滚动的刻度尺(四)
- bash命令之linux下并发运行任务
- Mac电脑快捷键(复制粘贴等)修改
- HDU-1212 Big Number
- 数据库中的BLOB字段--存图片等文件
- 说说 Hibernate 如何映射持久化类
- 自定义View-平滑滚动
- Android帧动画实现,防OOM,比原生动画集节约超过十倍的资源
- Android程序如何在任意处完全退出应用
- POJ1465:Multiple(BFS)
- 史上最全的 Sublime Text 汉化、插件安装合集
- webgis主界面及查询功能实现
- 钓鱼问题
- 使用sqoop1,将sqlserver数据导入hive
- Android团队协作中正确的启动Activity方法