[英]Vertical ViewPager and Android Pie Inconsistent Behavior with Swipe Gesture
我的问题与另外两个尚未回答的问题密切相关。
ViewPager没有响应在Fragment中动态创建的布局区域中的触摸
https://stackoverflow.com/questions/53469581/problem-with-vertical-viewpager-like-inshorts
我的垂直ViewPager在我测试的任何设备和任何操作系统5 - 8中运行得非常好。我最近使用Android Pie升级了像素2XL,现在我的Vertical ViewPager似乎没有响应,然后工作,然后就像失去焦点一样,然后工作。 拖动页面,它会移动并快速回到原始位置。 或者只是反弹回来。 再次,类似于上面链接的其他两个问题。
在Android 9之前,垂直滚动和分页是完美的。 我尝试使用反射并取得了一些成功。 它会刷得更好,似乎也不会失去焦点。 但是,如果我尝试用另一只手轻扫,它会停止,或者如果我改变了我滑动的位置,它将会停止。 这非常令人困惑。 我已经在运行Android 9的设备上添加了复制此问题所需的所有代码。
活动
public class FullscreenActivity extends AppCompatActivity {
VerticalViewPager verticalViewPager;
FragmentStatePagerExample fragmentStatePagerExample;
int pagerPadding;
/**
* Whether or not the system UI should be auto-hidden after
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
*/
private static final boolean AUTO_HIDE = true;
/**
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* Some older devices needs a small delay between UI widget updates
* and a change of the status and navigation bar.
*/
private static final int UI_ANIMATION_DELAY = 300;
private final Handler mHideHandler = new Handler();
private FrameLayout mContentView;
private final Runnable mHidePart2Runnable = new Runnable() {
@SuppressLint("InlinedApi")
@Override
public void run() {
// Delayed removal of status and navigation bar
// Note that some of these constants are new as of API 16 (Jelly Bean)
// and API 19 (KitKat). It is safe to use them, as they are inlined
// at compile-time and do nothing on earlier devices.
verticalViewPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
};
private View mControlsView;
private final Runnable mShowPart2Runnable = new Runnable() {
@Override
public void run() {
// Delayed display of UI elements
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.show();
}
mControlsView.setVisibility(View.VISIBLE);
}
};
private boolean mVisible;
private final Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
hide();
}
};
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
return false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
pagerPadding = getScreenDimension(this);
mVisible = true;
mControlsView = findViewById(R.id.fullscreen_content_controls);
verticalViewPager = findViewById(R.id.main_viewpager);
verticalViewPager.setPadding(0,0,0,pagerPadding);
verticalViewPager.setClipToPadding(false);
fragmentStatePagerExample = new FragmentStatePagerExample(getSupportFragmentManager());
verticalViewPager.setAdapter(fragmentStatePagerExample);
verticalViewPager.setCurrentItem(0);
// Set up the user interaction to manually show or hide the system UI.
verticalViewPager.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
toggle();
}
});
// Upon interacting with UI controls, delay any scheduled hide()
// operations to prevent the jarring behavior of controls going away
// while interacting with the UI.
findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
delayedHide(100);
}
private void toggle() {
if (mVisible) {
hide();
} else {
show();
}
}
private void hide() {
// Hide UI first
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
mControlsView.setVisibility(View.GONE);
mVisible = false;
// Schedule a runnable to remove the status and navigation bar after a delay
mHideHandler.removeCallbacks(mShowPart2Runnable);
mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
}
@SuppressLint("InlinedApi")
private void show() {
// Show the system bar
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mVisible = true;
// Schedule a runnable to display UI elements after a delay
mHideHandler.removeCallbacks(mHidePart2Runnable);
mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
}
/**
* Schedules a call to hide() in delay milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
}
private static int getScreenDimension(Context context)
{
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
int width = metrics.widthPixels;
int height = metrics.heightPixels;
return (int)Math.round(height * .2);
}
}
碎片
public class ImageFragment extends Fragment{
ImageView imageView;
String imageUrl = "";
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Bundle bundle = getArguments();
imageUrl = bundle.getString("url");
return inflater.inflate(R.layout.fragment_image, container,false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
imageView = (ImageView)view.findViewById(R.id.iv_imagefragment);
Glide.with(getActivity()).load(imageUrl).into(imageView);
}
public static Fragment getInstance(int position, String url){
Bundle bundle = new Bundle();
bundle.putString("url",url);
ImageFragment fragment = new ImageFragment();
fragment.setArguments(bundle);
return fragment;
}
}
ViewPager
public class VerticalViewPager extends ViewPager {
public VerticalViewPager(Context context) {
super(context);
init();
}
public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// The majority of the magic happens here
setPageTransformer(true, new VerticalPageTransformer());
setOffscreenPageLimit(2);
// The easiest way to get rid of the overscroll drawing that happens on the left and right
setOverScrollMode(OVER_SCROLL_NEVER);
}
private class VerticalPageTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(View view, float position) {
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
view.setAlpha(1);
// Counteract the default slide transition
view.setTranslationX(view.getWidth() * -position);
//set Y position to swipe in from top
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
/**
* Swaps the X and Y coordinates of your touch event.
*/
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;
ev.setLocation(newX, newY);
return ev;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev); // return touch coordinates to original reference frame for any child views
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(swapXY(ev));
}
}
ViewPager适配器
public class FragmentStatePagerExample extends FragmentStatePagerAdapter {
String url = "";
public FragmentStatePagerExample(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
switch (position){
case 0:
url = "https://images.unsplash.com/photo-1532977692289-827d858a170b?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=29b1d5377ad9db8de64b1b73d21812c7&auto=format&fit=crop&w=1474&q=80";
return ImageFragment.getInstance(position,url);
case 1:
url = "https://images.unsplash.com/photo-1533029516911-0458c644baea?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=0f618e036e338f48ef919b8fb86c5ba1&auto=format&fit=crop&w=701&q=80";
return ImageFragment.getInstance(position,url);
case 2:
url = "https://images.unsplash.com/photo-1532989622000-d4f013a215e1?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1a69643c04176376315714b9b2897de5&auto=format&fit=crop&w=677&q=80";
return ImageFragment.getInstance(position,url);
default:
url = "https://images.unsplash.com/photo-1532983819500-85d633c73b7a?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1f0b228b67f03064241534a6c65d9497&auto=format&fit=crop&w=1050&q=80";
return ImageFragment.getInstance(position,url);
}
}
@Override
public int getCount() {
return 4;
}
}
活动XML
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
tools:context=".FullscreenActivity">
<!-- This FrameLayout insets its children based on system windows using
android:fitsSystemWindows. -->
<com.david.verticalviewpagerexample.VerticalViewPager
android:id="@+id/main_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:id="@+id/fullscreen_content_controls"
style="?metaButtonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:background="@color/black_overlay"
android:orientation="horizontal"
tools:ignore="UselessParent">
<Button
android:id="@+id/dummy_button"
style="?metaButtonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dummy_button" />
</LinearLayout>
</FrameLayout>
片段XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_imagefragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
</LinearLayout>
更新:1
https://github.com/youngkaaa/YViewPagerDemo
还有另一个库,它在Android Pie上非常流畅,但它几乎没有软崩溃。 此外,它在API 19上崩溃。
更新:2
谷歌最近发布了带有androidx支持库https://developer.android.com/jetpack/androidx/releases/viewpager2的 ViewPager2,它支持垂直viewpager。 但是,它仍处于alpha版本,并且它有许多已知问题。
在花费了大量时间在SO上,尝试了许多GitHub库并等待某人对赏金做出回应之后,我提出了以下解决方案并希望它能帮助任何需要它的人。
起初, 这个问题引起了我的注意,我认为那里的大部分答案都很有用,因此我对它们进行了投票。 帮助我的主要答案是link-1和link-2 。
即使我必须进行一些小的更改来忽略水平滑动事件。
请提前尝试使用此代码并提供反馈,以便进一步改进。 快乐编码:)
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class VerticalViewPager extends ViewPager {
float x = 0;
float mStartDragX = 0;
private static final float SWIPE_X_MIN_THRESHOLD = 50; // Decide this magical nuber as per your requirement
public VerticalViewPager(Context context) {
super(context);
init();
}
public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// The majority of the magic happens here
setPageTransformer(true, new VerticalPageTransformer());
// The easiest way to get rid of the overscroll drawing that happens on the left and right
setOverScrollMode(OVER_SCROLL_NEVER);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (getAdapter() != null) {
if (getCurrentItem() >= 0 || getCurrentItem() < getAdapter().getCount()) {
swapXY(event);
final int action = event.getAction();
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
mStartDragX = event.getX();
if (x < mStartDragX
&& (mStartDragX - x > SWIPE_X_MIN_THRESHOLD)
&& getCurrentItem() > 0) {
Log.i("VerticalViewPager", "down " + x + " : " + mStartDragX + " : " + (mStartDragX - x));
setCurrentItem(getCurrentItem() - 1, true);
return true;
} else if (x > mStartDragX
&& (x - mStartDragX > SWIPE_X_MIN_THRESHOLD)
&& getCurrentItem() < getAdapter().getCount() - 1) {
Log.i("VerticalViewPager", "up " + x + " : " + mStartDragX + " : " + (x - mStartDragX));
mStartDragX = 0;
setCurrentItem(getCurrentItem() + 1, true);
return true;
}
break;
}
} else {
mStartDragX = 0;
}
swapXY(event);
return super.onTouchEvent(swapXY(event));
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = super.onInterceptTouchEvent(swapXY(event));
switch (event.getAction() & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
x = event.getX();
break;
}
swapXY(event); // return touch coordinates to original reference frame for any child views
return intercepted;
}
/**
* Swaps the X and Y coordinates of your touch event.
*/
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;
ev.setLocation(newX, newY);
return ev;
}
private class VerticalPageTransformer implements PageTransformer {
@Override
public void transformPage(View view, float position) {
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
view.setAlpha(1);
// Counteract the default slide transition
view.setTranslationX(view.getWidth() * -position);
//set Y position to swipe in from top
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
}
你可以尝试这个lib: VerticalViewPager ,这在我的项目中工作正常。
但是这个lib是从v19复制的,所以有些方法不存在,你可以自己实现。
问题似乎是#swapXY
交换x和y值的方式。 我相信ViewPager
使用的VelocityTracker
使用#getAxisValue
而不是#getAxisValue
#getX/#getY
,结果不会被交换。 但似乎没有办法设置轴值,也没有设置子类MotionEvent
所以我没有找到一种方法来坚持使用简单的#swapXY
解决方案。 当我检测到未穿孔状态时,我分叉了ViewPager
并使用VelocityTracker.getYVelocity
。
diff --git project/src/main/java/org/gc/project/util/ShopVerticalViewPager.java project/src/main/java/org/gc/project/util/ShopVerticalViewPager.java
index e5560a0..f23f9f7 100644
--- project/src/main/java/org/gc/project/util/MyVerticalViewPager.java
+++ project/src/main/java/org/gc/project/util/MyVerticalViewPager.java
@@ -179,4 +179,8 @@ public class ShopVerticalViewPager extends ViewPager {
return super.onTouchEvent( swapXY( ev ) );
}
+ public boolean isVerticalMode() {
+ return true;
+ }
+
}
\ No newline at end of file
diff --git project/src/main/java/gcandroid/support/v4/view/ViewPager.java project/src/main/java/gcandroid/support/v4/view/ViewPager.java
index 20e1448..4ae2d3c 100644
--- project/src/main/java/gcandroid/support/v4/view/ViewPager.java
+++ project/src/main/java/gcandroid/support/v4/view/ViewPager.java
@@ -205,6 +205,7 @@ public class ViewPager extends ViewGroup {
private int mMaximumVelocity;
private int mFlingDistance;
private int mCloseEnough;
+ private boolean mInvertedVelocityTrackerInVerticalMode = false;
// If the pager is at least this close to its final position, complete the scroll
// on touch down and let the user interact with the content inside instead of
@@ -391,6 +392,21 @@ public class ViewPager extends ViewGroup {
ViewCompat.setImportantForAccessibility(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
+
+ // tell if the velocity tracker is inverted in vertical mode (most probably uses MotionEvent.getAxisValue instead of MotionEvent.getX but
+ // I can't change these values nor inherit from MotionEvent)
+ VelocityTracker vt = VelocityTracker.obtain();
+ long time = SystemClock.uptimeMillis();
+ MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ vt.addMovement(ev);
+ ev.recycle();
+ ev = MotionEvent.obtain(time, time + 10, MotionEvent.ACTION_MOVE, 10, 0, 0);
+ ev.setLocation( 0, 10 );
+ vt.addMovement(ev);
+ ev.recycle();
+ vt.computeCurrentVelocity(1000, mMaximumVelocity);
+ mInvertedVelocityTrackerInVerticalMode = vt.getYVelocity() == 0;
+ vt.recycle();
}
@Override
@@ -2027,6 +2043,10 @@ public class ViewPager extends ViewGroup {
return mIsBeingDragged;
}
+ public boolean isVerticalMode() {
+ return false;
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mFakeDragging) {
@@ -2111,8 +2131,9 @@ public class ViewPager extends ViewGroup {
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
- velocityTracker, mActivePointerId);
+ int initialVelocity = (int) ( isVerticalMode() && mInvertedVelocityTrackerInVerticalMode ? VelocityTrackerCompat.getYVelocity( velocityTracker, mActivePointerId )
+ : VelocityTrackerCompat.getXVelocity( velocityTracker, mActivePointerId ) );
+
mPopulatePending = true;
final int width = getClientWidth();
final int scrollX = getScrollX();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.