web-dev-qa-db-ja.com

iOSでのオーバースクロール効果のようなAndroid

アプリにiOSのようなバウンスオーバースクロール効果を実装したい。

私はこれに遭遇しました link これはカスタムScrollViewの作成を提案します。しかし、問題は、高速で上下にスクロールしているときは正常に機能しているが、画面の下部または上部を引っ張るとすぐに動かなくなって、効果が機能しなくなることです。

私が達成したい種類のアニメーションの例として、これを見ることができます:

これは私が現在持っているコードです:

public class ObservableScrollView extends ScrollView
{
    private static final int MAX_Y_OVERSCROLL_DISTANCE = 150;

    private Context mContext;
    private int mMaxYOverscrollDistance;

    public ObservableScrollView(Context context)
    {
        super(context);
        mContext = context;
        initBounceScrollView();
    }

    public ObservableScrollView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        mContext = context;
        initBounceScrollView();
    }

    public ObservableScrollView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        mContext = context;
        initBounceScrollView();
    }

    private void initBounceScrollView()
    {
        //get the density of the screen and do some maths with it on the max overscroll distance
        //variable so that you get similar behaviors no matter what the screen size

        final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
        final float density = metrics.density;

        mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);
    }

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
    {
        //This is where the magic happens, we have replaced the incoming maxOverScrollY with our own custom variable mMaxYOverscrollDistance;
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverscrollDistance, isTouchEvent);
    }
}
10
Star

_CoordinatorLayout.Behavior_に基づく簡単なソリューションをすぐにまとめました。完璧ではありません。少し時間をかけて微調整することもできますが、悪くはありません。とにかく、結果は次のようになります。

enter image description here

回答を始める前のちょっとした補足として、通常のNestedScrollViewではなく、サポートライブラリのScrollViewを使用することを強くお勧めします。それらはまったく同じですが、NestedScrollViewは、より低いAPIレベルで正しいネストされたスクロール動作を実装します。

とにかく、私の答えから始めましょう:私が思いついた解決策は、ScrollViewListView、またはRecyclerViewなどのスクロール可能なコンテナで機能し、あなたはする必要はありませんViewsをサブクラス化して実装します。

まだ使用していない場合は、まずプロジェクトにGoogleのデザインサポートライブラリを追加する必要があります。

_compile 'com.Android.support:design:25.0.1'
_

APIレベル25(ちなみにこれを行う必要があります)を対象としていない場合は、APIレベルの最新バージョンを含める必要があります(たとえば、APIレベル24の場合は_compile 'com.Android.support:design:24.2.0'_)。

使用しているスクロール可能なコンテナは、レイアウトのCoordinatorLayoutでラップする必要があります。私の例では、NestedScrollViewを使用しています。

_<?xml version="1.0" encoding="utf-8"?>
<Android.support.design.widget.CoordinatorLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <Android.support.v4.widget.NestedScrollView
        Android:layout_width="match_parent"
        Android:layout_height="match_parent">

        <!-- content -->

    </Android.support.v4.widget.NestedScrollView>

</Android.support.design.widget.CoordinatorLayout>
_

CoordinatorLayoutを使用すると、Behaviorを直接の子ビューに割り当てることができます。この場合、オーバースクロールバウンス効果を実装するBehaviorNestedScrollViewを割り当てます。

Behaviorのコードを見てみましょう:

_public class OverScrollBounceBehavior extends CoordinatorLayout.Behavior<View> {

    private int mOverScrollY;

    public OverScrollBounceBehavior() {
    }

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

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        mOverScrollY = 0;
        return true;
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        if (dyUnconsumed == 0) {
            return;
        }

        mOverScrollY -= dyUnconsumed;
        final ViewGroup group = (ViewGroup) target;
        final int count = group.getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = group.getChildAt(i);
            view.setTranslationY(mOverScrollY);
        }
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        final ViewGroup group = (ViewGroup) target;
        final int count = group.getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = group.getChildAt(i);
            ViewCompat.animate(view).translationY(0).start();
        }
    }
}
_

Behaviorが何であり、どのように機能するかを説明することは、この回答の範囲を超えているため、上記のコードが何をするのかを簡単に説明します。 Behaviorは、CoordinatorLayoutの直接の子で発生するすべてのスクロールイベントをインターセプトします。 onStartNestedScroll()メソッドでは、スクロールイベントに関心があるため、trueを返します。 onNestedScroll()では、垂直スクロールのどの程度がスクロールコンテナーによって消費されなかったか(つまりオーバースクロール)を示すdyUnconsumedパラメーターを確認し、スクロールコンテナーの子を変換します。その量で。デルタ値を取得しているだけなので、mOverscrollY変数にすべてのデルタ値を合計する必要があります。 onStopNestedScroll()は、スクロールイベントが停止したときに呼び出されます。これは、スクロールコンテナーのすべての子を元の位置にアニメーション化するときです。

BehaviorNestedScrollViewに割り当てるには、_layout_behavior_ xml属性を使用して、使用するBehaviorの完全なクラス名を渡す必要があります。私の例では、上記のクラスはパッケージ_com.github.wrdlbrnft.testapp_にあるので、値として_com.github.wrdlbrnft.testapp.OverScrollBounceBehavior_を設定する必要があります。 _layout_behavior_はCoordinatorLayoutのカスタム属性であるため、正しい名前空間をプレフィックスとして付ける必要があります。

_<?xml version="1.0" encoding="utf-8"?>
<Android.support.design.widget.CoordinatorLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <Android.support.v4.widget.NestedScrollView
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        app:layout_behavior="com.github.wrdlbrnft.testapp.OverScrollBounceBehavior">

        <!-- content -->

    </Android.support.v4.widget.NestedScrollView>

</Android.support.design.widget.CoordinatorLayout>
_

CoordinatorLayoutに追加した名前空間と、NestedScrollViewに追加した_app:layout_behavior_属性に注意してください。

そして、それはあなたがしなければならないすべてです!この回答は意図したよりも長いことが判明しましたが、CoordinatorLayoutおよびBehaviorsに関する基本事項の一部はスキップしました。そのため、これらに慣れていない場合や、その他の質問がある場合は、遠慮なく質問してください。

27
Xaver Kapeller

Xaver Kapellerのおかげで、kotlinandroidx

enter image description here

コーディネーターの依存関係を追加する

implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"

CoordinatorLayout.Behaviorを拡張する新しいクラスを作成します

import Android.content.Context
import Android.util.AttributeSet
import Android.view.View
import Android.view.ViewGroup
import Android.view.animation.AccelerateDecelerateInterpolator
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat

class OverScrollBehavior(context: Context, attributeSet: AttributeSet)
: CoordinatorLayout.Behavior<View>() {

companion object {
    private const val OVER_SCROLL_AREA = 4
}

private var overScrollY = 0

override fun onStartNestedScroll(
    coordinatorLayout: CoordinatorLayout,
    child: View,
    directTargetChild: View,
    target: View,
    axes: Int,
    type: Int
): Boolean {
    overScrollY = 0
    return true
}

override fun onNestedScroll(
    coordinatorLayout: CoordinatorLayout,
    child: View,
    target: View,
    dxConsumed: Int,
    dyConsumed: Int,
    dxUnconsumed: Int,
    dyUnconsumed: Int,
    type: Int,
    consumed: IntArray
) {
    if (dyUnconsumed == 0) {
        return
    }

    overScrollY -= (dyUnconsumed/OVER_SCROLL_AREA)
    val group = target as ViewGroup
    val count = group.childCount
    for (i in 0 until count) {
        val view = group.getChildAt(i)
        view.translationY = overScrollY.toFloat()
    }
}

override fun onStopNestedScroll(
    coordinatorLayout: CoordinatorLayout,
    child: View,
    target: View,
    type: Int
) {
    // Smooth animate to 0 when the user stops scrolling
    moveToDefPosition(target)
}

override fun onNestedPreFling(
    coordinatorLayout: CoordinatorLayout,
    child: View,
    target: View,
    velocityX: Float,
    velocityY: Float
): Boolean {
    // Scroll view by inertia when current position equals to 0
    if (overScrollY == 0) {
        return false
    }
    // Smooth animate to 0 when user fling view
    moveToDefPosition(target)
    return true
}

private fun moveToDefPosition(target: View) {
    val group = target as ViewGroup
    val count = group.childCount
    for (i in 0 until count) {
        val view = group.getChildAt(i)
        ViewCompat.animate(view)
            .translationY(0f)
            .setInterpolator(AccelerateDecelerateInterpolator())
            .start()
    }
}

}

CoordinatorLayoutとNestedScrollViewを使用してXMLファイルを作成する

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    tools:context=".MainActivity">
    <androidx.core.widget.NestedScrollView
        Android:id="@+id/scrollView"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        app:layout_behavior=".OverScrollBehavior">
        <TextView
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:textAlignment="center"
            Android:padding="10dp"
            Android:text="@string/Lorem"/>
    </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

そして、追加することを忘れないでください

app:layout_behavior=".OverScrollBehavior" // Or your file name

nestedScrollView XMLマークアップへのフィールド

0
lincollincol