22

我有一个垂直的nestedscrollview,其中包含一堆带有水平布局管理器设置的recyclerview。这个想法与新的 google play 商店的外观非常相似。我能够使其正常工作,但它一点也不流畅。以下是问题:

1)即使我点击它,水平recyclerview项目大多数时候都无法拦截触摸事件。滚动视图似乎优先于大多数动作。我很难抓住水平运动。这个 UX 令人沮丧,因为我需要尝试几次才能正常工作。如果你查看 play store,它能够很好地拦截触摸事件,而且效果很好。我注意到在 Play Store 中,他们设置的方式是在一个垂直回收视图中包含许多水平回收视图。没有滚动视图。

2)水平recyclerviews的高度必须手动设置,并且没有简单的方法来计算子元素的高度。

这是我正在使用的布局:

<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,其中 ppl 说将列表放在列表中是一个坏主意,但它是一种非常常见且现代的 UI 模式,到处都在使用。想想 netflix 之类的界面,里面有一系列水平滚动列表一个垂直列表。没有一种顺利的方法来实现这一点吗?

来自商店的示例图片:

谷歌游戏商店

4

3 回答 3

23

因此,平滑滚动问题现在已修复。它是由设计支持库(当前为 23.1.1)中的 NestedScrollView 中的错误引起的。

您可以在此处阅读有关该问题和简单修复的信息: https ://code.google.com/p/android/issues/detail?id=194398

简而言之,在您执行投掷之后,nestedscrollview 没有在滚动组件上注册一个完整的,因此它需要一个额外的“ACTION_DOWN”事件来释放父级nestedscrollview 拦截(吃掉)后续事件。所以发生的情况是,如果您尝试滚动您的子列表(或 viewpager),在一次投掷后,第一次触摸会释放父 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 文件中的 nestedscrollview,子列表应正确拦截和处理触摸事件。

呼,实际上有很多这样的错误让我想完全放弃设计支持库,并在它更成熟时重新访问它。

于 2015-12-16T11:28:12.563 回答
1

由于falc0nit3解决方案不再起作用(当前项目使用28.0.0支持库的版本),我找到了另一个。

问题的背景原因仍然相同,可滚动视图通过true在第二次点击时返回而在向下事件中吃掉,它不应该,因为自然第二次点击在 fling 视图上停止滚动并且可以与下一个move事件一起使用以开始相反滚动问题的重现与 与NestedScrollView一样RecyclerView。我的解决方案是在本机视图能够拦截它之前手动停止滚动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)
    }
}

对于RecyclerView

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)
    }

}

尽管解决方案RecyclerView看起来很容易阅读,NestedScrollView但它有点复杂。不幸的是,没有明确的方法可以在小部件中手动停止滚动,唯一的职责是管理滚动(omg)。我对abortAnimatedScroll()方法很感兴趣,但它是私人的。可以使用反射来解决它,但对我来说更好的是调用方法,它会调用abortAnimatedScroll()自己。看看onTouchEvent处理ACTION_DOWN

 /*
 * 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();
}

基本上停止投掷是在这种方法中管理的,但稍晚一点,我们必须调用它来修复错误

不幸的是,由于这个原因,我们不能只是OnTouchListener在外面创建和设置它,所以只有继承符合要求

于 2019-01-29T16:00:09.767 回答
0

我已经成功地使用 ViewPager 在垂直滚动的父级中进行水平滚动:

<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"/>

公共类 UniversityKnownForPagerAdapter 扩展 PagerAdapter {

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);
}

唯一问题:您必须为视图寻呼机提供固定高度

于 2015-12-14T04:54:05.333 回答