web-dev-qa-db-ja.com

垂直ViewPagerおよびAndroidスワイプジェスチャーでのパイの一貫性のない動作

私の問題は、まだ回答されていない他の2つの質問と密接に関連しています。

ViewPagerがFragmentで動的に作成されたレイアウト領域のタッチに応答しない

https://stackoverflow.com/questions/53469581/problem-with-vertical-viewpager-like-inshorts

私の垂直ViewPagerは、テストしたすべてのデバイスとOS 5〜8で一貫して素晴らしく、一貫して動作します。最近、ピクセル2XLをAndroid Pieでアップグレードしましたが、垂直ViewPagerが応答しないようです。機能すると、フォーカスを失ったように機能し、機能します。ページをドラッグすると、ページが移動して元の位置に戻ります。または、跳ね返ります。ここでも、上記の他の2つの質問と同様です。

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

Googleは最近、androidxサポートライブラリ https://developer.Android.com/jetpack/androidx/releases/viewpager2 を備えたViewPager2をリリースしました。これは垂直ビューページャーをサポートします。ただし、それはまだアルファ版であり、多くの既知の問題があります。

12

SOに非常に多くの時間を費やし、多くのGitHubライブラリを試し、誰かが賞金に反応するのを待った後、以下の解決策を考え出し、それが必要な人を助けることを願っています。

最初は this の質問が私の注意を引いており、そこにあるほとんどの回答は参考になると思うので、私はそれらを支持しました。私を助けた主な答えは link-1link-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);
            }
        }
    }
}
8
Chitrang

あなたはこのlibを試すことができます: VerticalViewPager 、これは私のプロジェクトではうまくいきます。

しかし、このlibはv19からコピーされているため、一部のメソッドは存在しません。自分で実装できます。

0
Rui Cai

問題は、#swapXYがx値とy値を交換する方法にあるようです。 VelocityTrackerで使用されているViewPager#getAxisValueではなく#getX/#getYを使用していると思います。結果は入れ替えられません。しかし、軸の値を設定する方法もMotionEventをサブクラス化する方法もないようなので、単純な#swapXYソリューションを使用する方法が見つかりませんでした。私はViewPagerをforkし、スワップされていない状態を検出したときに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();
0
user517708