Horizo​​ntalScrollView 中的捕捉效果

Snapping Effect in HorizontalScrollView

我想在 HorizontalScrollView 中实现 Snapping effect,即当用户水平滚动时,最明显的项目(项目可见度 > 50%)会出现在中间。

我尝试使用:

hsv.getViewTreeObserver().addOnScrollChangedListener(
    new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            int scrollX = hsv.getScrollX(); // For HorizontalScrollView
            Log.e("scrollX",String.valueOf(scrollX));
            // DO SOMETHING WITH THE SCROLL COORDINATES
        }
    }
);

但即使我们不触摸屏幕,该值也不是恒定的。

这是logcat的一部分:

03-28 11:11:22.116 26639-26639/package_name E/scrollX: 0
03-28 11:11:22.133 26639-26639/package_name E/scrollX: 792
03-28 11:11:22.133 26639-26639/package_name E/scrollX: 0
03-28 11:11:22.151 26639-26639/package_name E/scrollX: 795
03-28 11:11:22.151 26639-26639/package_name E/scrollX: 0
03-28 11:11:22.166 26639-26639/package_name E/scrollX: 799
03-28 11:11:22.166 26639-26639/package_name E/scrollX: 0
03-28 11:11:22.183 26639-26639/package_name E/scrollX: 801
03-28 11:11:22.183 26639-26639/package_name E/scrollX: 0
03-28 11:11:22.199 26639-26639/package_name E/scrollX: 803
03-28 11:11:22.199 26639-26639/package_name E/scrollX: 0
03-28 11:11:22.216 26639-26639/package_name E/scrollX: 804
03-28 11:11:22.216 26639-26639/package_name E/scrollX: 0
03-28 11:11:22.233 26639-26639/package_name E/scrollX: 805
03-28 11:11:22.233 26639-26639/package_name E/scrollX: 0
03-28 11:11:22.249 26639-26639/package_name E/scrollX: 806
03-28 11:11:22.249 26639-26639/package_name E/scrollX: 0

我已经尝试过这些解决方案,I am not getting the pointI don't know to do it:

  1. HorizontalScrollView within ScrollView Touch Handling
  2. Horizo​​ntalScrollView 有贴合效果
  3. Creating Custom Horizontal Scroll View With Snap or paging
  4. Creating a “Snapping” Horizontal Scroll View

我的用例: 我有一个 HorizontalScrollView 连接到 Recyclerview(Vertical) 的适配器,所以 snapHelper 可以垂直完成,但我不知道如何让它水平。

Here 是捕捉项目的自定义水平滚动视图的完整代码。

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

import java.util.ArrayList;

public class HomeFeatureLayout extends HorizontalScrollView {
    private static final int SWIPE_MIN_DISTANCE = 5;
    private static final int SWIPE_THRESHOLD_VELOCITY = 300;

    private ArrayList mItems = null;
    private GestureDetector mGestureDetector;
    private int mActiveFeature = 0;

    public HomeFeatureLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public HomeFeatureLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public HomeFeatureLayout(Context context) {
        super(context);
    }

    public void setFeatureItems(ArrayList items){
        LinearLayout internalWrapper = new LinearLayout(getContext());
        internalWrapper.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        internalWrapper.setOrientation(LinearLayout.HORIZONTAL);
        addView(internalWrapper);
        this.mItems = items;
        for(int i = 0; i< items.size();i++){
            LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.homefeature,null);
            //...
          //Create the view for each screen in the scroll view
            //...
            internalWrapper.addView(featureLayout);
        }
        setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //If the user swipes
                if (mGestureDetector.onTouchEvent(event)) {
                    return true;
                }
                else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
                    int scrollX = getScrollX();
                    int featureWidth = v.getMeasuredWidth();
                    mActiveFeature = ((scrollX + (featureWidth/2))/featureWidth);
                    int scrollTo = mActiveFeature*featureWidth;
                    smoothScrollTo(scrollTo, 0);
                    return true;
                }
                else{
                    return false;
                }
            }
        });
        mGestureDetector = new GestureDetector(new MyGestureDetector());
    }
        class MyGestureDetector extends SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            try {
                //right to left
                if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    int featureWidth = getMeasuredWidth();
                    mActiveFeature = (mActiveFeature < (mItems.size() - 1))? mActiveFeature + 1:mItems.size() -1;
                    smoothScrollTo(mActiveFeature*featureWidth, 0);
                    return true;
                }
                //left to right
                else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    int featureWidth = getMeasuredWidth();
                    mActiveFeature = (mActiveFeature > 0)? mActiveFeature - 1:0;
                    smoothScrollTo(mActiveFeature*featureWidth, 0);
                    return true;
                }
            } catch (Exception e) {
                    Log.e("Fling", "There was an error processing the Fling event:" + e.getMessage());
            }
            return false;
        }
    }
}

此示例以编程方式添加视图并调用它们 Features。但是您可以简单地更改该行为并使用 getChildrenCount() 而不是 mItems.size() 等等。

重要的部分是 GestureDetectorTouchListener。在 TouchListener 中,您可以侦听 ACTION_UP,即当用户的手指移开时(如滚动后),您可以根据滚动量及其位置计算哪个视图是 active。您还可以添加一个 GestureDetector 来捕获投掷操作并在那里执行相同的操作。

基于@AdibFaramarzi 的回答,这是一个您自己创建内部 LinearLayout 并动态添加/删除项目的版本:

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

import java.util.ArrayList;

public class SnapHorizontalScrollView extends HorizontalScrollView {
    private static final int SWIPE_MIN_DISTANCE = 5;
    private static final int SWIPE_THRESHOLD_VELOCITY = 300;

    private final ArrayList<LinearLayout> mItems = new ArrayList<>();
    private GestureDetector mGestureDetector;
    private int mActiveFeature = 0;

    public SnapHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public SnapHorizontalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SnapHorizontalScrollView(Context context) {
        super(context);
    }

    public void removeFeatureItem(LinearLayout featureLayout) {
        this.mItems.remove(featureLayout);
    }

    @SuppressLint("ClickableViewAccessibility")
    public void addFeatureItem(LinearLayout featureLayout) {
        this.mItems.add(featureLayout);
        setOnTouchListener((v, event) -> {
            //If the user swipes
            if (mGestureDetector.onTouchEvent(event)) {
                return true;
            }
            else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
                int scrollX = getScrollX();
                int featureWidth = v.getMeasuredWidth();
                mActiveFeature = ((scrollX + (featureWidth / 2)) / featureWidth);
                int scrollTo = mActiveFeature * featureWidth;
                smoothScrollTo(scrollTo, 0);
                return true;
            }
            else{
                return false;
            }
        });
        mGestureDetector = new GestureDetector(new MyGestureDetector());
    }
    class MyGestureDetector extends SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            try {
                // right to left
                if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    int featureWidth = getMeasuredWidth();
                    mActiveFeature = (mActiveFeature < (mItems.size() - 1))? mActiveFeature + 1:mItems.size() -1;
                    smoothScrollTo(mActiveFeature*featureWidth, 0);
                    return true;
                }
                // left to right
                else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    int featureWidth = getMeasuredWidth();
                    mActiveFeature = (mActiveFeature > 0)? mActiveFeature - 1:0;
                    smoothScrollTo(mActiveFeature * featureWidth, 0);
                    return true;
                }
            } catch (Exception e) {
                Log.e("Fling", "There was an error processing the Fling event:" + e.getMessage());
            }
            return false;
        }
    }
}

在XML中,这将代替 Horizo​​ntalScrollView 实现,然后通常的 LinearLayout 将被放置在其中。对于以下内容,我们假设您将 internalWrapper 设置为内部 LinearLayout 的变量。

向 LinearLayout 添加项目时,在 internalWrapper.addView(layout) 之后使用 snapScroller.addFeatureItem(layout),同样在 internalWrapper.removeView(layout) 之后使用 snapScroller.removeFeatureItem(layout)