我有一个垂直的nestedscrollview,其中包含一堆带有水平布局管理器设置的回收站视图。这个想法与新的google play商店的外观非常相似。我能够让它发挥作用,但它一点也不流畅。以下是问题:
1) 水平回收视图项目在大多数情况下都无法拦截触摸事件,即使我直接点击它。滚动视图似乎优先于大多数运动。我很难在水平运动中勾手。这种用户体验令人沮丧,因为我需要在它工作之前尝试几次。如果你检查游戏商店,它能够很好地拦截触摸事件,而且效果很好。我注意到,在游戏商店里,他们设置的方式是在一个垂直的回收站里面放置许多水平的回收站。无滚动视图。
2) 水平循环视图的高度必须手动设置,并且没有简单的方法来计算子元素的高度。
以下是我使用的布局:
<android.support.v4.widget.NestedScrollView
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:background="@color/dark_bgd"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/main_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="gone"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/starring_list"
android:paddingLeft="@dimen/spacing_major"
android:paddingRight="@dimen/spacing_major"
android:layout_width="match_parent"
android:layout_height="180dp" />
这种UI模式是非常基本的,很可能在许多不同的应用程序中使用。我读过很多SO,人们说把一个列表放在一个列表中是一个坏主意,但这是一个非常普遍和现代的UI模式,到处都在使用。想象一下网飞的界面,在一个垂直列表中有一系列水平滚动列表。难道没有一个平稳的方法来完成这个吗?
商店中的示例图像:
因此,平滑滚动问题现已解决。它是由设计支持库(当前为 23.1.1)中的 NestedScrollView 中的一个错误引起的。
你可以在这里阅读这个问题和简单的解决方法:https://code.google.com/p/android/issues/detail?id=194398
简而言之,在您执行 fling 之后,嵌套滚动视图没有在滚动器组件上注册一个完整的,因此它需要一个额外的“ACTION_DOWN”事件来释放父嵌套滚动视图,使其免于拦截(吞噬)后续事件。因此,如果您尝试滚动子列表(或查看页),则在投掷后,第一次触摸会释放父 NSV 绑定,后续触摸将起作用。这使得用户体验变得非常糟糕。
本质上需要在NSV的ACTION_DOWN事件上添加这一行:
computeScroll();
这是我正在使用的:
public class MyNestedScrollView extends NestedScrollView {
private int slop;
private float mInitialMotionX;
private float mInitialMotionY;
public MyNestedScrollView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
ViewConfiguration config = ViewConfiguration.get(context);
slop = config.getScaledEdgeSlop();
}
public MyNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyNestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private float xDistance, yDistance, lastX, lastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
lastX = ev.getX();
lastY = ev.getY();
// This is very important line that fixes
computeScroll();
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
xDistance += Math.abs(curX - lastX);
yDistance += Math.abs(curY - lastY);
lastX = curX;
lastY = curY;
if (xDistance > yDistance) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
}
使用这个类代替xml文件中的嵌套滚动视图,子列表应该拦截并正确处理触摸事件。
呸,实际上有很多这样的bug让我想彻底放弃设计支持库,等到它更成熟时再重新访问它。
由于falc0nit3解决方案不再工作(目前该项目使用< code>28.0.0版本的支持库),我找到了另一个。
问题的背景原因仍然是一样的,可滚动视图通过在第二次点击时返回 true
来吃掉向下事件,而不应该,因为自然而然地,第二次点击 fling 视图停止滚动,并可能与下一步事件
一起使用以开始相反的滚动该问题与 NestedScrollView
和回收器视图
一样重现。我的解决方案是在本机视图能够在 onInterceptTouchEvent
中拦截它之前停止手动滚动。在这种情况下,它不会吃掉ACTION_DOWN
事件,因为它已经停止了。
因此,对于NestedScrollView
:
class NestedScrollViewFixed(context: Context, attrs: AttributeSet) :
NestedScrollView(context, attrs) {
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
onTouchEvent(ev)
}
return super.onInterceptTouchEvent(ev)
}
}
对于< code > recycle view :
class RecyclerViewFixed(context: Context, attrs: AttributeSet) :
RecyclerView(context, attrs) {
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
if (e.actionMasked == MotionEvent.ACTION_DOWN) {
this.stopScroll()
}
return super.onInterceptTouchEvent(e)
}
}
尽管< code > recycle view 的解决方案看起来很容易阅读,但对于< code>NestedScrollView来说,它有点复杂。不幸的是,在widget中没有明确的方法来手动停止滚动,widget唯一的职责就是管理scroll (omg)。我对< code > abortAnimatedScroll()方法感兴趣,但它是私有的。可以使用反射来绕过它,但对我来说更好的方法是调用方法,该方法调用< code > abortAnimatedScroll()本身。查看< code>ACTION_DOWN的< code>onTouchEvent处理:
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
Log.i(TAG, "abort animated scroll");
abortAnimatedScroll();
}
基本上停止 fling 是用这种方法管理的,但稍晚一点,我们必须调用它来修复错误
不幸的是,由于这个原因,我们不能只创建< code>OnTouchListener并在外部设置它,所以只有继承符合要求
我成功地在一个垂直滚动的父视图中实现了水平滚动:
<android.support.v4.widget.NestedScrollView
...
<android.support.v4.view.ViewPager
android:id="@+id/pager_known_for"
android:layout_width="match_parent"
android:layout_height="350dp"
android:minHeight="350dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:clipToPadding="false"/>
公共类UniverassiyNotnForPager患儿扩展Pager患儿{
public UniversityKnownForPagerAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View rootView = mInflater.inflate(R.layout.card_university_demographics, container, false);
...
container.addView(rootView);
return rootView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
@Override
public int getCount() {
return 4;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
唯一的问题:您必须为视图寻呼机提供固定的高度