Как поддерживать полноэкранный интерфейс навигации для RecyclerView? - PullRequest
1 голос
/ 30 октября 2019

Фон

Я хотел поддержать полноэкранный интерфейс навигации, как показано здесь:

https://medium.com/androiddevelopers/windowinsets-listeners-to-layouts-8f9ccc8fa4d1 https://developer.android.com/guide/navigation/gesturenav

Проблема

Несмотря на то, что все действия моего приложения работали нормально, я неожиданно обнаружил проблемный объект с большими пальцами RecyclerView.

Здесь у меня есть 2 странные проблемы:

  1. При прокрутке квнизу, последние элементы не отображаются полностью.

enter image description here

В ландшафтном режиме большие пальцы выходят за пределы того места, куда я могу добраться, поэтому они также не доступны. Мало того, но я также могу видеть нормальную полосу прокрутки, и обе исчезают при прокрутке вниз:

enter image description here

Что яЯ пытался

Я пытался применить вставки к различным представлениям, включая отступы и поля, но ничего не помогло.

Кроме того, на некоторых веб-сайтах я видел, что я должен использовать View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION ив некоторых, что мне нужно также добавить View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN. Не помогло.

Вот мой текущий код (проект доступен здесь , так как я думаю, что это ошибка):

MainActivity.kt

class MainActivity : AppCompatActivity(R.layout.activity_main) {
    inline fun View.updateMargins(@Px left: Int = marginLeft, @Px top: Int = marginTop, @Px right: Int = marginRight, @Px bottom: Int = marginBottom) {
        updateLayoutParams<ViewGroup.MarginLayoutParams> {
            this.bottomMargin = bottom
            this.topMargin = top
            this.leftMargin = left
            this.rightMargin = right
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setSupportActionBar(toolbar)
        findViewById<View>(android.R.id.content).systemUiVisibility =
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        ViewCompat.setOnApplyWindowInsetsListener(appBarLayout) { _, insets ->
            val systemWindowInsets = insets.systemWindowInsets
            appBarLayout.updateMargins(
                left = systemWindowInsets.left,
                top = systemWindowInsets.top,
                right = systemWindowInsets.right
            )
            insets
        }

        ViewCompat.setOnApplyWindowInsetsListener(recyclerView) { _, insets ->
            val systemWindowInsets = insets.systemWindowInsets
            recyclerView.updatePadding(
                left = systemWindowInsets.left,
                bottom = systemWindowInsets.bottom,
                right = systemWindowInsets.right
            )
            insets
        }

        recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            init {
                setHasStableIds(true)
            }

            override fun onCreateViewHolder(
                parent: ViewGroup,
                viewType: Int
            ): RecyclerView.ViewHolder {
                return object : RecyclerView.ViewHolder(
                    LayoutInflater.from(this@MainActivity).inflate(R.layout.list_item, parent, false)
                ) {}
            }

            override fun getItemId(position: Int): Long = position.toLong()

            override fun getItemCount(): Int = 100

            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                holder.itemView.imageView.setColorFilter(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
                holder.itemView.textView.text = "item $position"
            }

        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menu.add("test").setIcon(android.R.drawable.ic_dialog_email).setOnMenuItemClickListener {
            true
        }.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
        return super.onCreateOptionsMenu(menu)
    }
}

styles.xml (я использую AppTheme в манифесте в качестве темы)

<resources xmlns:tools="http://schemas.android.com/tools">

    <style name="AppTheme" parent="Theme.MaterialComponents.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:navigationBarColor" tools:targetApi="lollipop">
            @android:color/transparent
        </item>
        <item name="android:statusBarColor" tools:targetApi="lollipop">@color/colorPrimaryDark
        </item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
</resources>

activity_main.xml

<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">


    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout" android:layout_width="match_parent" android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">
        <!--app:popupTheme="@style/AppTheme.PopupOverlay"-->
        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary" />
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent"
        android:orientation="vertical" android:scrollbars="vertical" app:fastScrollEnabled="true"
        app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
        app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
        app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
        app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:itemCount="100"
        tools:listitem="@layout/list_item" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

line.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">

    <solid android:color="@android:color/darker_gray" />

    <padding
        android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" />
</shape>

line_drawable.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/line" android:state_pressed="true" />

    <item android:drawable="@drawable/line" />
</selector>

thumb.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <corners
        android:bottomLeftRadius="44dp" android:topLeftRadius="44dp" android:topRightRadius="44dp" />
    <padding
        android:paddingLeft="22dp" android:paddingRight="22dp" />
    <solid android:color="@color/colorPrimaryDark" />
</shape>

thumb_drawable.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/thumb" android:state_pressed="true" />

    <item android:drawable="@drawable/thumb" />
</selector>

Вопросы

  1. Почему это происходит? Он отлично работал в других местах ...
  2. Как я могу заставить RecyclerView избегать обоих этих случаев, но разрешить навигационной панели внизу отображать содержимое RecyclerView, поскольку оно прозрачное?
  3. В каких случаях я должен добавить флаг View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN? Что он делает, чтобы помочь в этих случаях?

РЕДАКТИРОВАТЬ: возможный обходной путь состоит в том, чтобы избежать использования CoordinatorLayout. Это работает хорошо, но я хотел сделать вещи "официальным способом". Вот этот обходной путь:

Вместо CoordinatorLayout я использовал:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
    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">

...

И в коде я установил поля и отступы для RecyclerView:

    ViewCompat.setOnApplyWindowInsetsListener(recyclerView) { _, insets ->
        val systemWindowInsets = insets.systemWindowInsets
        recyclerView.updatePadding(
            bottom = systemWindowInsets.bottom
        )
        recyclerView.updateMargins(
            left = systemWindowInsets.left,
            right = systemWindowInsets.right
        )
        insets
    }

Ответы [ 2 ]

1 голос
/ 02 ноября 2019

Поскольку в моем предыдущем ответе обсуждается комментарий, я не буду его редактировать (это также может кому-то помочь).

Самое простое, что нужно сделать, - это позволить AppBarLayout обработать верхи нижние вставки. Мы можем сделать это, используя android:fitsSystemWindows="true" на AppBarLayout. Для элементов, видимых через панель навигации, используйте android:clipToPadding="false" на RecyclerView. Используйте android:scrollbars="none" на RecyclerView, чтобы отключить обычную полосу прокрутки. 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary" />

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:orientation="vertical"
        android:scrollbars="none"
        app:fastScrollEnabled="true"
        app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
        app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
        app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
        app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

В упражнении / фрагменте нам все равно придется обрабатывать нижнюю системную вставку для RecyclerView и левую и правую системную вставку для RecyclerView и AppBarLayout. We use margins for theRecyclerView` для быстрой прокрутки в область содержимого.

ViewCompat.setOnApplyWindowInsetsListener(recyclerView) { _, insets ->
            val systemWindowInsets = insets.systemWindowInsets
            appBarLayout.updatePadding(left = systemWindowInsets.left, right = systemWindowInsets.right)
            recyclerView.updatePadding(bottom = systemWindowInsets.bottom)
            recyclerView.updateMargins(left = systemWindowInsets.left, right = systemWindowInsets.right)
            insets
        }

Видео результат здесь, комментарий # 27 .

1 голос
/ 01 ноября 2019

Стоит отметить, что вы, вероятно, установили android:clipToPadding=false" в RecyclerView, хотя его выше не видно (в противном случае элементы не будут полностью видны за панелью навигации).

Первая часть

Первая часть (нижняя обивка в портретном режиме) очень легко решается. Похоже, что CoordinatorLayout каким-то образом сдвигает RecyclerView вниз на верхнюю вставку (очень вероятно, вызванный ScrollingViewBehavior, не занимался исследованием). Поэтому решение состоит в том, чтобы добавить нижнюю и верхнюю вставку к нижнему отступу рециркулятора:

ViewCompat.setOnApplyWindowInsetsListener(recyclerView) { _, insets -> 
    val systemWindowInsets = insets.systemWindowInsets
    recyclerView.updatePadding(
        left = systemWindowInsets.left,
        // Fix CoordinatorLayout behavior
        bottom = systemWindowInsets.bottom + systemWindowInsets.top
        right = systemWindowInsets.right
    )
    insets
}

Объяснение второй части

Вторая часть немного сложнее. Более простой частью являются двойные полосы прокрутки, но затем появляется быстрая прокрутка за панелью навигации. Заполнение - это часть вида (проверьте инспектор макета в Android Studio, вы увидите RecyclerView, простирающийся до панели навигации). Когда реализация быстрой прокрутки RecyclerView (основанная на ItemDecoration) отображает строку и большой палец, она не учитывает заполнение.

RecyclerView проверяет атрибут app:fastScrollEnabled="true" и вызывает initFastScroller(...) метод, который создает FastScroller объект (см. здесь ). Однако вы не можете расширить этот класс, так как он имеет аннотацию @VisibleForTesting (информация аннотации здесь , исходный код для FastScroller здесь ).

Решение длявторая часть

Исправление двойных полос прокрутки

Простая вещь, которую нужно решить, - двойные полосы прокрутки. Вы должны отключить обычные полосы прокрутки и показывать только быстрые полосы прокрутки. Чтобы сделать это, используйте android:scrollbars="none" в RecyclerView вместо android:scrollbars="vertical".

Исправление быстрой прокрутки

Для быстрой прокрутки, то, что вы можете сделать, это скопировать код из FastScroller и измените некоторые вещи для учета заполнения. Обратите внимание , что это также изменит портретный режим - панель быстрой прокрутки будет распространяться только на панель навигации. Окончательный код для так называемого FastScroller2 здесь здесь на pastebin (он немного длинный, поэтому я не буду его вставлять здесь). Просто создайте новый класс Java с именем FastScroller2 и вставьте код.

Затем вы можете использовать FastScroller2 вот так (вызывается из onCreate)

private fun setupRecyclerView() {
    recyclerView.adapter = ... // Your adapter here
    val thumbDrawable = ContextCompat.getDrawable(this, R.drawable.thumb_drawable) as StateListDrawable?
    val lineDrawable = ContextCompat.getDrawable(this, R.drawable.line_drawable)
    val thickness = resources.getDimensionPixelSize(R.dimen.fastScrollThickness)
    val minRange = resources.getDimensionPixelSize(R.dimen.fastScrollMinimumRange)
    val margin = resources.getDimensionPixelSize(R.dimen.fastScrollMargin)
    if (thumbDrawable != null && lineDrawable != null) {
        // No need to do anything else, the fast scroller will take care of 
        // "connecting" itself to the recycler view
        FastScroller2(recyclerView, thumbDrawable, lineDrawable,
            thumbDrawable, lineDrawable, thickness, minRange, margin, true)
    }
}

Толщина,Измерения minRange и margin были скопированы из библиотеки представления рециркулятора, см. здесь , добавлено в dimensions.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="fastScrollThickness">8dp</dimen>
    <dimen name="fastScrollMargin">0dp</dimen>
    <dimen name="fastScrollMinimumRange">50dp</dimen>
</resources>

Это также означает, что вам не нужен код быстрой прокрутки вмакет xml:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:orientation="vertical"
    android:scrollbars="none"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
    tools:itemCount="100"
    tools:listitem="@layout/list_item" />

Надеюсь, это вам поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...