web-dev-qa-db-ja.com

Android ViewPagerのイベントリスナーデータバインディング

Androidバインド機能を使用して、setOnPageChangeListenerのハンドラーをXMLファイルのViewPagerにバインドすることは可能ですか?

デモにはonClickイベントが表示されますが、それを使用して実装できるイベント機能の量に興味があります。データバインディングの機能に関するリンクも素晴らしいでしょう。ありがとう。

架空の例:

example_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:Android="http://schemas.Android.com/apk/res/Android">

<data>
   <variable name="handlers" type="com.example.Handlers"/>
</data>

<Android.support.v4.view.ViewPager
    Android:id="@+id/pager"
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"

    Android:onPageChangeListener="@{handlers.pageChanged}" />
</layout>

Handler.Java

package com.example.viewmodels;

import Android.view.View;

public class Handlers {
    public void pageChanged(View view){}
}

コンパイルエラーは次のとおりです。

エラー:(62)パッケージ「Android」の属性「onPageChangeListener」のリソース識別子が見つかりません

11

これを行うことは可能です。 AndroidサポートライブラリのViewクラス用に事前定義されたBindingAdapterクラスがないため、カスタムバインディングアダプターを実装する必要があります。

カスタムアダプタの実装方法については、 this をお読みください。

コードは次のようになります。私はそれらを徹底的にテストしていません。

<Android.support.v4.view.ViewPager
    Android:id="@+id/pager"
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    app:onPageChangeListener="@{handlers}" />

BindingAapterコード:

@BindingAdapter("onPageChangeListener")
public static void setOnPageChangeListener(ViewPager viewPager, ViewPager.OnPageChangeListener listener) {
    Log.i("setOnPageChangeListener");

    // clear listeners first avoid adding duplicate listener upon calling notify update related code
    viewPager.clearOnPageChangeListeners();
    viewPager.addOnPageChangeListener(listener);
}

P.S.渡されるハンドラークラスはViewPager.OnPageChangeListenerを実装する必要があります。

10
David Cheung

これが究極の解決策です。このクラスをプロジェクトに入れるだけです。

@BindingMethods({
    @BindingMethod(type = ViewPager.class, attribute = "Android:offscreenPageLimit", method = "setOffscreenPageLimit"),
    @BindingMethod(type = ViewPager.class, attribute = "Android:adapter", method = "setAdapter"),
    @BindingMethod(type = ViewPager.class, attribute = "Android:currentPage", method = "setCurrentItem"),
})
public final class ViewPagerBindingAdapter {

    @InverseBindingAdapter(attribute = "Android:currentPage", event = "Android:currentPageAttrChanged")
    public static int getCurrentPage(@NonNull final ViewPager pager) {
        return pager.getCurrentItem();
    }

    @BindingAdapter(value = {"Android:onPageScrolled", "Android:onPageSelected", "Android:onPageScrollStateChanged",
        "Android:currentPageAttrChanged"}, requireAll = false)
    public static void onSetAdapter(@NonNull final ViewPager pager, final OnPageScrolled scrolled, final OnPageSelected selected,
        final OnPageScrollStateChanged scrollStateChanged, final InverseBindingListener currentPageAttrChanged) {

        final ViewPager.OnPageChangeListener newValue;
        if (scrolled == null && selected == null && scrollStateChanged == null && currentPageAttrChanged == null) {
            newValue = null;
        } else {
            newValue = new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
                    if (scrolled != null) {
                        scrolled.onPageScrolled(position, positionOffset, positionOffsetPixels);
                    }
                }

                @Override
                public void onPageSelected(final int position) {
                    if (selected != null) {
                        selected.onPageSelected(position);
                    }
                    if (currentPageAttrChanged != null) {
                        currentPageAttrChanged.onChange();
                    }
                }

                @Override
                public void onPageScrollStateChanged(final int state) {
                    if (scrollStateChanged != null) {
                        scrollStateChanged.onPageScrollStateChanged(state);
                    }
                }
            };
        }
        final ViewPager.OnPageChangeListener oldValue = ListenerUtil.trackListener(pager, newValue, R.id.page_change_listener);
        if (oldValue != null) {
            pager.removeOnPageChangeListener(oldValue);
        }
        if (newValue != null) {
            pager.addOnPageChangeListener(newValue);
        }
    }

    public interface OnPageScrolled {
        void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
    }

    public interface OnPageSelected {
        void onPageSelected(int position);
    }

    public interface OnPageScrollStateChanged {
        void onPageScrollStateChanged(int state);
    }

    private ViewPagerBindingAdapter() {
        throw new UnsupportedOperationException();
    }
}

また、リソースにidリソースを追加します。

<resources>
    <item name="page_change_listener" type="id"/>
</resources>

その後、次のようにxmlで使用できるようになります。

<Android.support.v4.view.ViewPager
                Android:layout_width="match_parent"
                Android:layout_height="match_parent"
                Android:currentPage="@={viewModel.currentPage}"
                Android:offscreenPageLimit="@{viewModel.offscreenPageLimit}"
                Android:onPageSelected="@{currentPage -> viewModel.pageSelected(currentPage)}"
                Android:adapter="@{adapter}"/>

ご覧のとおり、currentPageには逆バインディングがあるため、viewModelは現在のページを設定し、ユーザーがスワイプすると現在のページを取得することもできます。

11
b1n0m

OnPageChangeListenerの代わりに、SimpleOnPageChangeListenerを使用して、関心のあるメソッドのみをオーバーライドできます。

@BindingAdapter("onPageChanged")
@JvmStatic
fun addPageChangedListener(viewPager: ViewPager, listener: OnPageChanged) {
    viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
        override fun onPageSelected(position: Int) {
            listener.onPageChanged(position)
        }
    })
}

interface OnPageChanged {
    fun onPageChanged(position: Int)
}

レイアウト:

<androidx.viewpager.widget.ViewPager
    app:onPageChanged="@{viewModel::handlePageChanged}">

ハンドラ:

fun handlePageChanged(position: Int) {
    println("Page changed: $position")
}

他の人が指摘しているように、重複するリスナーを添付しないように注意することをお勧めします。

1
Big McLargeHuge