WebView与ListView滑动冲突——(二)LinearLayout控制WebView滚动
来源:互联网 发布:qq邮箱软件下载 编辑:程序博客网 时间:2024/05/29 06:50
上一篇我们大致了解了一下View中 事件的一些基础《WebView与ListView滑动冲突——(一)事件基础篇》,Scroll是为了实现View平滑滚动的一个Helper类,通常在自定义View中使用。 这次我们从一下几个方面来了解一下Scroll与VelocityTracker的用法:
- View和MotionEvent的位置信息
- View中的Scroll方法
- Scroll中的scroll*()方法
- touchSlop与VelocityTracker
- LinearLayout控制WebView滚动
通过上面几个方面的学习,将会对View的滑动有一定的认知。废话不多说了,开始
View和MotionEvent中的位置信息
在我们自定义View中,经常要获取各种位置坐标,但是View的get方法那么多,我也不知道这个get方法获取的到底是不是那个位置坐标,我能怎么办,我也很绝望呀。所以就整理了一些View和MotionEvent中的一些获取位置信息的一些方法。
View的位置信息
View中的获取位置信息的get方法。有些是不能直接在Activity的onCreate()中调用,因为当时View还未绘制完成,这个时候调用View的get方法,获取到的位置信息当然是0了。一般可以在Activity的onWindowFocusChanged()
方法中获取,或者使用延迟策略去获取。
下面我们来看一下View中的通过那些信息来定位View的位置
left、right、top、bottom、elevation
这五个参数表示的是View的原始位置距离父控件边缘的距离,并且无论这个View被移动到了什么位置,或者被缩放、旋转了多少,这五个值都是永久不变的
- left:目标View的最左边和这个View所在父控件的最左边的距离,通过view.getLeft()方法获取;
- right:目标View的最右边和这个View所在父控件的最左边的距离,通过view.getRight()方法获取;
- top:目标View的最上边和这个View所在父控件的最上边的距离,通过view.getTop()方法获取;
- bottom:目标View的最下边和这个View所在父控件的最上边的距离,通过view.getBottom()方法获取;
- elevation:目标View的Z轴高度和这个View所在的父控件所在的Z轴高度的距离,通过view.getElevation()方法获取(这个属性是Android 5.0之后添加的新属性)
撒?你说还是不明白,没事。我也没明白。还是用一张图来认识一下把:
translationX、translationY、translationZ
这三个参数代表的是在动画或者滑动View的时候,View的当前位置相对于其原始位置平移的距离:
- translationX:在滑动过程中,View当前位置的最左边和这个View原始位置的最左边的距离,通过view.getTranslationX()方法获取;
- translationY:在滑动过程中,View当前位置的最上边和这个View原始位置的最上边的距离,通过view.getTranslationY()方法获取;
- translationZ:在动画过程中,View当前位置的Z轴高度和这个View原始Z轴高度的距离,通过view.getTranslationZ()方法获取(这个方法是Android 5.0之后添加的新方法)。
x、y、z
这三个参数代表的是View的当前位置相对于其父控件的距离:
- x:目标View的当前位置的最左边和这个View所在父布局的最左边的距离,通过view.getX()方法获取;
- y:目标View的当前位置的最上边和这个View所在父布局的最上边的距离,通过view.getY()方法获取;
- z:目标View的当前位置的Z轴位置和这个View所在父布局的Z轴位置的距离,通过view.getZ()方法获取(这个方法是Android 5.0之后添加的新方法)。
这三个参数和前面的几个参数的关系公式如下:
x = left + translationX;y = top + translationY;z = elevation + translationZ;
MotionEvent的位置信息
使用MotionEvent类,我们还可以获取到触摸屏幕时View的一些位置参数:
- x:当前触摸的位置相对于目标View的X轴坐标,通过getX()方法获取;
- y:当前触摸的位置相对于目标View的Y轴坐标,通过getY()方法获取;
- rawX:当前触摸的位置相对于屏幕最左边的X轴坐标,通过getRawX()方法获取;
- rawY:当前触摸的位置相对于屏幕最上边的Y轴坐标,通过getRawY()方法获取。
下面也通过一张图来清楚的认识一下把,要不然看完还是懵逼+懵逼=懵逼²。。。
好了。对于View和MotionEvent的位置信息就简单说这么多把,够用就行。再说下去就 下笔千言离题万里了(关键是就知道这么多了- -!)
View中的Scroll方法
Android中为了实现View的滑动,系统为此提供了scrollTo()和scrollBy()两个方法。打开源码来让我们一探究竟。
/** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */ public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } } /** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
scrollTo()方法的注释中,我们可以看到,这个方法将会调用onScrollChanged()方法并且这个View将会被重绘,这就达到了View的内容位置变化效果。而scrollBy(),这货就比较懒了,内部直接调用了scrollTo()方法。不过两者的区别还是很明显的,scrollTo()是直接一步到位,而scrollBy()则是慢慢的累积。
mScrollX和mScrollY则是View的偏移量。而且都是指当前view的内容相对view本身左上角起始坐标的偏移量。看了下面这幅图你就明白了:
来个小结:
- scrollTo()的移动是一步到位,而scrollBy()逐步累加的
- scrollTo()和scrollBy()传递的参数是偏移量而非坐标
scrollTo()和scrollBy()移动的都只是View的内容,View的背景本身是不移动的。
注:在实际操作中你会发现,传入的参数完全跟想执行的操作相反。所以如果我们想往x轴和y轴正方向移动时,mScrollY和mScrollX必须为负值,相反如果我们想往x轴和y轴负方向移动时,mScrollY和mScrollX就必须为正值
TouchSlop与VelocityTracker
TouchSlop
在自定义View中,我们有时会用到下面这行代码:
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
那么这个方法获取到的int值到底是个啥啊?通过方法上面的注释才知道,TouchSlop是一个滑动距离的常量值,并且需要注意的是不同的设备,touchSlop的值可能是不同的,一切以上述的函数获取为准。
这个值对于自定义View有什么用呢?我们来举个例子,当你按下屏幕后,不上下滑动,也不松开,而是手指左右上下抖一抖,会一直触发ACTION_MOVE事件,只有当滑动距离大于TouchSlop这个值时,系统才认为我们做了滑动操作。
VelocityTracker
VelocityTracker是一个滑动速率的Helper类。它可以辅助跟踪触摸事件的速率,比如快速滑动或者其他的滑动手势。我们一般会在ACTION_DOWN事件中初始化VelocityTracker对象,比如:
@Override public boolean onTouchEvent(MotionEvent event) { VelocityTracker mVelocityTracker =VelocityTracker.obtain(); //收集速率追踪点数据 velocityTracker.addMovement(event); switch (event.getAction()) { ... case MotionEvent.ACTION_DOWN: break; ... } }
在ACTION_MOVE事件中我们就可以获取当前事件的滑动速率了,关于computeCurrentVelocity(int units,int maxVelocity)
units : 我们想要指定的得到的速度单位,如果值为1,代表1毫秒运动了多少像素。如果值为1000,代表1秒内运动了多少像素。如果值为100,代表100毫秒内运动了多少像素。(这个参数设置真有点…….什么鬼嘛!)这个方法还有一个重载函数 computeCurrentVelocity (int units, float maxVelocity), 跟上面一样也就是多了一个参数。
maxVelocity : 该方法所能得到的最大速度,这个速度必须和你指定的units使用同样的单位,而且必须是整数.也就是,你指定一个速度的最大值,如果计算超过这个最大值,就使用这个最大值,否则,使用计算的的结果,
注:最后一定别忘了在ACTION_UP或者ACTION_CANCEL中回收一下
//获取最大速度mMaxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity()switch (event.getAction()) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //计算瞬时速度 mVelocityTracker .computeCurrentVelocity(units, mMaxVelocity); float velocityX = mVelocityTracker .getXVelocity(); float velocityY = mVelocityTracker .getYVelocity(); if (mVelocityTracker != null) { mVelocityTracker.clear(); mVelocityTracker.recycle(); mVelocityTracker = null; } break; ...}
LinearLayout控制WebView滚动
实现View简单的移动
注意,是移动,是移动(难不成还是联通- -!)而不是滚动。达到这种效果只需要View的scrollTo或者scrollBy即可实现,不是很复杂。直接看代码把:
package com.tianzhao.demo;import android.content.Context;import android.graphics.Color;import android.support.annotation.AttrRes;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.view.Gravity;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.widget.LinearLayout;import android.widget.TextView;import java.util.Locale;public class CustomView extends LinearLayout { public CustomView(@NonNull Context context) { super(context); init(context); } public CustomView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } public CustomView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { setOrientation(VERTICAL); initChildView(context); } private void initChildView(Context context) { for (int i = 0; i < 60; i++) { TextView view = new TextView(context); LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,120); view.setLayoutParams(params); view.setText(String.format(Locale.CHINA,"%d",i)); view.setGravity(Gravity.CENTER); if (i % 2 == 1) { view.setBackgroundColor(Color.parseColor("#FF40FF9C")); } else { view.setBackgroundColor(Color.parseColor("#303F9F")); } addView(view); } } private float mLastY; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: int offsetY = (int) (event.getY() - mLastY); ((View) getParent()).scrollBy(0, -offsetY); break; } return true; }}
来看看效果图,只要手指停下。就不移动了,达不到View滚动的效果。
实现View的滚动
注意,这里说的是滚动、是滚动、滚动、动。不是移动!
我们在 onInterceptTouchEvent()
方法中主要处理事件的一些初始化,和是否拦截当前事件的相关操作。而 onTouchEvent()
则主要处理事件的滑动与滚动。不必要的代码我已经剔除了。只留了最基本有用的代码,注释也补充了。雅酷,伊利卡通,代码,开!
package com.tianzhao.demo;import android.content.Context;import android.support.annotation.AttrRes;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.ViewConfiguration;import android.webkit.WebView;import android.widget.LinearLayout;import android.widget.Scroller;/** * Desc:使用LinearLayout的事件来操控WebView的滚动 * Created by tianzhao on 2017/8/31. */public class CustomView extends LinearLayout { private static final String TAG = "CustomView"; private static final boolean DEBUG_LOG = true; private Scroller mScroll; private VelocityTracker mVelocityTracker; private int mTouchSlop; private int mMinimumVelocity; private int mMaximumVelocity; private WebView mWebView; public CustomView(@NonNull Context context) { super(context); init(context); } public CustomView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } public CustomView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { mScroll = new Scroller(context); ViewConfiguration viewConfiguration = ViewConfiguration.get(context); mTouchSlop = viewConfiguration.getScaledTouchSlop(); mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity(); mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity(); initChildView(context); } private void initChildView(Context context) { mWebView = new WebView(context); mWebView.loadUrl("file:///android_asset/index.html"); mWebView.setFocusable(false); addView(mWebView); } /** * 初始化值,并且选择是否拦截事件 */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean isIntercept = false; /* * 这里不能使用event.getAction() * 注意event.getAction()和event.getActionMasked()的区别 * 当MotionEvent对象只包含一个触摸点的事件时,上边两个函数的结果是相同的, * 但是当包含多个触摸点时,二者的结果就不同啦。 * getAction获得的int值是由pointer的index值和事件类型值组合而成的, * 而getActionWithMasked则只返回事件的类型值 */ switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: boolean flingFinished = stopFling(); mTouchLastY = event.getY(); mPointerID = event.getPointerId(0); if (!flingFinished) { return true; } else { addVelocityTracker(event); } break; case MotionEvent.ACTION_MOVE: int pointerIndex = event.findPointerIndex(mPointerID); if (pointerIndex < 0) { break; } int offsetY = (int) (mTouchLastY - event.getY(pointerIndex)); mTouchLastY = event.getY(); //判断上下滑动距离是否大于最小滑动距离 //如果大于,则将事件向下传递给onTouchEvent处理 //反之则进行拦截,不进行任何响应 if (Math.abs(offsetY) >= mTouchSlop) { isIntercept = true; } break; case MotionEvent.ACTION_POINTER_DOWN: isIntercept = true; break; } return isIntercept; } private float mTouchLastY; private int mPointerID; @Override public boolean onTouchEvent(MotionEvent event) { addVelocityTracker(event); //这里不能使用event.getAction() //注意event.getAction()和event.getActionMasked()的区别 switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: //将当前LinearLayout的滑动事件透传给WebView去进行滚动 int pointerIndex = event.findPointerIndex(mPointerID); int offsetY = (int) (mTouchLastY - event.getY(pointerIndex)); mTouchLastY = event.getY(pointerIndex); mWebView.scrollBy(0, offsetY); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: //手指离开时,计算当前的滑动速率 //使用Scroll和VelocityTracker进行滚动 startFling(-(int) getScrollVelocityY()); recycleVelocityTracker(); break; case MotionEvent.ACTION_POINTER_UP: //当两个触摸点,有一个离开屏幕时 //将当前的触摸点 solvePointerUp(event); break; } return true; } private void solvePointerUp(MotionEvent event) { // 获取离开屏幕的手指的索引 int pointerIndexLeave = event.getActionIndex(); int pointerIdLeave = event.getPointerId(pointerIndexLeave); // 离开屏幕的手指如果正是目前的有效手指,此处需要重新调整 if (mPointerID == pointerIdLeave) { // 将还在屏幕的手指标记为有效手指,并且重置VelocityTracker int reIndex = pointerIndexLeave == 0 ? 1 : 0; mPointerID = event.getPointerId(reIndex); // 调整触摸位置,防止出现跳动 mTouchLastY = event.getY(reIndex); // 清除mVelocityTracker记录的信息 clearVelocity(); } } /** * VelocityTracker也有最大值和最小值 * computeCurrentVelocity获取到的只 <= mMaximumVelocity * 所以需要判断是否大于最小值 */ private float getScrollVelocityY() { float velocityY = 0; if (mVelocityTracker != null) { mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); velocityY = mVelocityTracker.getYVelocity(mPointerID); print("getScrollVelocityY : " + velocityY); } if (Math.abs(velocityY) > mMinimumVelocity) { return velocityY; } return 0; } private int mLastFlingY; private void startFling(int velocityY) { mLastFlingY = 0; print(" startFling : " + velocityY); mScroll.fling(0, 0, 0, velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE); //注意此处一定要调用postInvalidate()或者invalidate(),置于两者的区别,这块就不多做解释了 postInvalidate(); } private boolean stopFling() { if (mScroll != null && !mScroll.isFinished()) { mScroll.abortAnimation();//停止滚动 return false; } return true; } @Override public void computeScroll() { //此处一定要通过 mScroll.computeScrollOffset() 方法判断是否滚动结束 //要不然发生死循环就GG了 if (mScroll != null && mScroll.computeScrollOffset()) { int y = mScroll.getCurrY(); print(" offsetY : " + (y - mLastFlingY)); mWebView.scrollBy(0, y - mLastFlingY); mLastFlingY = y; //注意此处同上一样一定要调用postInvalidate()或者invalidate() //以达成一种循环的效果 postInvalidate(); } } private void addVelocityTracker(MotionEvent event) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } private void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.clear(); mVelocityTracker.recycle(); mVelocityTracker = null; } } private void clearVelocity() { if (mVelocityTracker != null) { mVelocityTracker.clear(); } } private void print(String msg) { if (DEBUG_LOG) { Log.d(TAG, msg); } }}
这次来看一下最终的效果
这节就说道这里把,下节来正式进入主题——WebView与ListView共存。
- WebView与ListView滑动冲突——(二)LinearLayout控制WebView滚动
- ListView嵌套webview滑动冲突
- WebView与ListView滑动冲突——(一)事件基础篇
- swipeRefreshLayout与WebView,ListView滑动冲突解决方法总结
- webview与swiperefreshlayout滑动冲突
- ListView嵌套webView滑动冲突的解决
- webview与viewpager的滑动冲突问题
- ScrollView 与 recyclerView、WebView的滑动冲突
- ListView WebView Viewpager GridView ScrollView 滑动冲突解决
- Android ViewPager、ScrollView或ListView嵌套WebView滑动冲突
- 防止viewpager和子view滑动冲突(ScrollView嵌套webview,Listview嵌套Listview 滑动事件冲突)
- iewpager 嵌套 webview 滑动冲突
- Android Viewpager与WebView轮播滑动冲突的解决方案
- webview向上滚动与下拉刷新冲突(X5WebView)
- ActivityGroup HorizontalScrollVew WebView 滑动事件冲突!
- android scrollview嵌套webview滑动冲突问题
- ScrollView嵌套WebView滑动冲突问题解决
- Android中ScrollView和WebView滑动冲突
- 改bug过程中的新发现,重新认识String trim方法
- Android学习-获取APK包名的几种方法
- Redis超级新手指南-下篇(哪吒篇)
- problem:浏览器中显示的内容,和通过右键看到的网页源码不一样?(未解决)
- 虫洞攻击
- WebView与ListView滑动冲突——(二)LinearLayout控制WebView滚动
- Django安装和使用
- swiper 解决动态加载数据滑动失效
- MySQL修改wait_timeout参数
- 微信小程序 获取小程序码和二维码java接口开发
- 超级详细Tcpdump 的用法
- “System.Linq.IQueryable<int>”不包含“Contains”的定义 或者“System.Linq.IEnumberable<int>”不包含“Contains”的定义
- Utils総括
- mk路线