HorizontalScrollView 中的捕捉效果
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 point
或 I don't know to do it
:
- HorizontalScrollView within ScrollView Touch Handling
- HorizontalScrollView 有贴合效果
- Creating Custom Horizontal Scroll View With Snap or paging
- 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()
等等。
重要的部分是 GestureDetector
和 TouchListener
。在 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中,这将代替 HorizontalScrollView 实现,然后通常的 LinearLayout 将被放置在其中。对于以下内容,我们假设您将 internalWrapper
设置为内部 LinearLayout 的变量。
向 LinearLayout 添加项目时,在 internalWrapper.addView(layout)
之后使用 snapScroller.addFeatureItem(layout)
,同样在 internalWrapper.removeView(layout)
之后使用 snapScroller.removeFeatureItem(layout)
我想在 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 point
或 I don't know to do it
:
- HorizontalScrollView within ScrollView Touch Handling
- HorizontalScrollView 有贴合效果
- Creating Custom Horizontal Scroll View With Snap or paging
- 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()
等等。
重要的部分是 GestureDetector
和 TouchListener
。在 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中,这将代替 HorizontalScrollView 实现,然后通常的 LinearLayout 将被放置在其中。对于以下内容,我们假设您将 internalWrapper
设置为内部 LinearLayout 的变量。
向 LinearLayout 添加项目时,在 internalWrapper.addView(layout)
之后使用 snapScroller.addFeatureItem(layout)
,同样在 internalWrapper.removeView(layout)
之后使用 snapScroller.removeFeatureItem(layout)