Подтвердить что ты не робот

Когда вы рисуете за пределами рамки клипа, связанную с Android: как я могу предотвратить недоразвитие представлений от рисования поверх моего пользовательского представления?

Я написал пользовательский вид Android, который нужно рисовать вне его границ.

Это то, что у меня есть:

enter image description here

Это то, что происходит, когда я нажимаю кнопку, произношу правую кнопку:

enter image description here

Как я могу предотвратить просмотр ниже, чтобы нарисовать поверх моего "дескриптора"?

Следующий связанный псевдокод из моего проекта.

Мой пользовательский вид MyHandleView рисует вот так:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Path p = mPath;
    int handleWidth = mHandleWidth;
    int handleHeight = mHandleHeight;
    int left = (getWidth() >> 1) - handleWidth;

    canvas.save();

    // allow drawing out of bounds vertically
    Rect clipBounds = canvas.getClipBounds();
    clipBounds.inset(0, -handleHeight);
    canvas.clipRect(clipBounds, Region.Op.REPLACE);

    // translate up to draw the handle
    canvas.translate(left, -handleHeight);

    // draw my background shape
    canvas.drawPath(p, mPaint);

    canvas.restore();
}

Макет что-то вроде этого (я немного упростил):

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Main content of the SlidingUpPanel -->
    <fragment
        android:[email protected]+id/panel"
        class="com.neosperience.projects.percassi.android.home.HomeFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?android:attr/actionBarSize"
        tools:layout="@layout/fragment_home" />

    <!-- The Sliding Panel -->
    <LinearLayout
        android:id="@id/panel"
        android:layout_width="match_parent"
        android:layout_height="@dimen/myFixedSize"
        android:alignParentBottom="true"
        android:orientation="vertical"
        android:clipChildren="false">

        <MyHandleView  xmlns:custom="http://schemas.android.com/apk/res-auto.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            custom:handleHeight="@dimen/card_panel_handle_height"
            custom:handleWidthRatio="@dimen/card_panel_handle_width_ratio"
            custom:handleBackgroundColor="#000"/>

        <TextView
            android:id="@+id/loyaltyCardPanelTitle"
            android:layout_width="match_parent"
            android:layout_height="@dimen/card_panel_height"
            android:background="#000"
            android:gravity="center"
            android:textStyle="bold"
            android:textSize="24sp"
            android:textColor="#fff"
            android:text="My TEXT"/>
    </LinearLayout>

</RelativeLayout>

Вы можете представить фрагмент как представление, содержащее две кнопки внизу, в LinearLayout.

Вместо внешнего RelativeLayout я действительно использую представление из библиотеки: SlidingUpPanelLayout (https://github.com/umano/AndroidSlidingUpPanel). Но я тестировал поведение с этим RelativeLayout: то же самое, что означает, что библиотека не связана.

Я говорю это просто, чтобы вы знали, что я не могу просто разместить там FrameLayout и избегать рисования вне границ отсечения.

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

Я хотел бы иметь возможность аннулировать мой пользовательский просмотр всякий раз, когда любое другое "близкое" (или любое) представление становится недействительным, поэтому мне нужен какой-то прослушиватель при недействительности вида, но я не знаю о нем.

Может кто-нибудь помочь?

4b9b3361

Ответ 1

Я нашел решение самостоятельно, даже если это не оптимально для выступлений.

Просто добавьте:

android:clipChildren="false"

в RelativeLayout (или любой другой макет, который у вас есть).

У этого есть 2 эффекта (может быть больше, это два, которые меня интересовали): - ViewGroup не обрезает рисунок своих детей (очевидно) - ViewGroup не проверяет пересечение с грязными регионами (недействительными) при рассмотрении того, какие дети перерисовывают

Я выкопал код представления о недействительности.

Процесс идет, больше или как, например:

  • a Вид недействителен сам, область, которую он обычно рисует (прямоугольник), становится "грязной областью" для перерисовки
  • В представлении отображается его родительский элемент (какая-то ViewGroup), он должен перерисовать себя
  • родители делают то же самое с родительским корнем
  • каждый родительский элемент в цикле иерархии для каждого дочернего элемента и проверьте, не пересекаются ли грязные области некоторых из них.
  • если он также перерисовывает их

В шаге 4 задействуется обрезка: ViewGroup проверяет границы своего дочернего объекта, только если clipChildren имеет значение true: это означает, что если вы поместите его в false, то всегда перерисовывает все его дочерние элементы, когда какая-либо из них недействительна.

Итак, моя иерархия View была такой:

ViewGroup A
|
|----- fragment subtree (containing buttons, map,
|       whatever element that I don't want to draw
|       on top of my handle)
|
|----- ViewGroup B
       |
       |---- my handle (which draw outside its clip bounds)

В моем случае "дескриптор" рисует его, связанный сверху, поверх того, что обычно вытягивается каким-то элементом поддерева фрагмента.

Если какой-либо вид внутри фрагмента недействителен, он передает свою "грязную область" вверх в дереве просмотра, и каждая группа просмотра проверяет, есть ли в этом регионе некоторые другие дети.

ViewGroup B будет кликать то, что я рисую вне границ клипа, если я не устанавливаю на нем clipBounds = "false".

Если что-то получится недействительным в поддереве фрагмента, ViewGroup A увидит, что грязная область ViewGroup B не пересекает область поддерева фрагмента и пропустит перерисовку ViewGroup B.

Но если я также скажу ViewGroup A о том, чтобы не зацикливать дочерние элементы, он все равно предоставит ViewGroup B команду invalidate, которая затем приведет к перерисовке моего дескриптора.

Итак, решение состоит в том, чтобы установить

android:clipChildren="false"

в любой группе ViewGroup в иерархии над представлением, которая вычеркивает из нее границы, по которым контент может упасть "под" областью вне привязки, которую вы рисуете.

Очевидным побочным эффектом этого является то, что всякий раз, когда я аннулирую любое представление внутри ViewGroup A, вызов invalidate будет перенаправлен с недопустимой областью ко всему виду в нем.

Однако любое представление, которое не пересекает грязную область, находящуюся внутри ViewGroup с clipChildren = "true" (по умолчанию), будет пропущено.

Итак, чтобы избежать проблем с производительностью при выполнении этого, убедитесь, что в ваших группах просмотра с clipChildren = "true" не так много "стандартных" прямых детей. И со "стандартным" я имею в виду, что они не выходят за рамки их взглядов.

Так, например, если в моем примере ViewGroup B содержит много видов, рассмотрим возможность обертывания всех из них в ViewGroup с помощью clipChildren = "true" и оставим только эту группу представлений и одно представление, которое выводит за пределы его региона в качестве прямых дочерних элементов ViewGroup B. То же самое относится к ViewGroup A.

Этот простой факт гарантирует, что ни один другой вид не получит перерисовку, если они не находятся в недействительной грязной области, минимизирующей необходимые перерисовывания.

Я все еще открыт, чтобы услышать больше внимания, если у кого-то есть;)

Поэтому я подожду немного, прежде чем отмечать это как принятый ответ.

EDIT: многие устройства выполняют что-то другое при обработке clipChildren = "false". Я обнаружил, что мне пришлось установить clipChildren = "false" во всех родительских представлениях моего пользовательского виджета, который может содержать элементы в их иерархии, которые должны нарисовать "вне связанной области" виджета, или вы можете увидеть свой собственный рисунок показывая ON TOP другого представления, которое должно было его покрыть. Например, в моем макете у меня был навигационный ящик, который должен был покрывать мою "ручку". Если я не установил clipChildren = "false" в макете NavigationDrawer, я иногда вижу, что мой дескриптор всплывает перед открытым ящиком.

EDIT2: Мой пользовательский виджет имел 0 высоту и рисовал "сверху" сам по себе. Хорошо работали на устройствах Nexus, но у многих из них была определенная "оптимизация", которая полностью пропускает рисунок, имеющий ширину 0 или 0. Поэтому имейте это в виду, если вы хотите написать компонент, который извлекается из него: вы должны назначить его как минимум 1 пиксель высоты/ширины.