首页 > 解决方案 > 滚动行为 coordinatorLayout 自定义视图

问题描述

在此处输入图像描述

我需要这种类型的行为来实现。图像应该滚动并使用 wtsapp 之类的文本设置到中心。但在 wtsapp 中它设置为左对齐,我需要设置为中心。我怎样才能做到这一点?

在此处输入图像描述

滚动后的图像将在工具栏中显示类似的文本。(提到)

标签: android

解决方案


1. CoordinatorLayout 和 AppBarLayout 的行为

public class AvatarImageBehavior extends CoordinatorLayout.Behavior<ImageView> {

    // calculated from given layout
    private int startXPositionImage;
    private int startYPositionImage;
    private int startHeight;
    private int startToolbarHeight;

    private boolean initialised = false;

    private float amountOfToolbarToMove;
    private float amountOfImageToReduce;
    private float amountToMoveXPosition;
    private float amountToMoveYPosition;

    // user configured params
    private float finalToolbarHeight, finalXPosition, finalYPosition, finalHeight;
    private boolean onlyVerticalMove;

    public AvatarImageBehavior(
            final Context context,
            final AttributeSet attrs) {

        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AvatarImageBehavior);
            finalXPosition = a.getDimension(R.styleable.AvatarImageBehavior_finalXPosition, 0);
            finalYPosition = a.getDimension(R.styleable.AvatarImageBehavior_finalYPosition, 0);
            finalHeight = a.getDimension(R.styleable.AvatarImageBehavior_finalHeight, 0);
            finalToolbarHeight = a.getDimension(R.styleable.AvatarImageBehavior_finalToolbarHeight, 0);
            onlyVerticalMove = a.getBoolean(R.styleable.AvatarImageBehavior_onlyVerticalMove, false);
            a.recycle();
        }
    }

    @Override
    public boolean layoutDependsOn(@NotNull final CoordinatorLayout parent, @NotNull final ImageView child, @NotNull final View dependency) {
        return dependency instanceof AppBarLayout; // change if you want another sibling to depend on
    }

    @Override
    public boolean onDependentViewChanged(@NotNull final CoordinatorLayout parent, @NotNull final ImageView child, @NotNull final View dependency) {

        // make child (avatar) change in relation to dependency (toolbar) in both size and position, init with properties from layout
        initProperties(child, dependency);

        // calculate progress of movement of dependency
        float currentToolbarHeight = startToolbarHeight + dependency.getY(); // current expanded height of toolbar
        // don't go below configured min height for calculations (it does go passed the toolbar)
        currentToolbarHeight = Math.max(currentToolbarHeight, finalToolbarHeight);
        final float amountAlreadyMoved = startToolbarHeight - currentToolbarHeight;
        final float progress = 100 * amountAlreadyMoved / amountOfToolbarToMove; // how much % of expand we reached

        // update image size
        final float heightToSubtract = progress * amountOfImageToReduce / 100;
        CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        lp.width = (int) (startHeight - heightToSubtract);
        lp.height = (int) (startHeight - heightToSubtract);
        child.setLayoutParams(lp);

        // update image position
        final float distanceXToSubtract = progress * amountToMoveXPosition / 100;
        final float distanceYToSubtract = progress * amountToMoveYPosition / 100;
        float newXPosition = startXPositionImage - distanceXToSubtract;
        //newXPosition = newXPosition < endXPosition ? endXPosition : newXPosition; // don't go passed end position
        if (!onlyVerticalMove) {
            child.setX(newXPosition);
        }
        child.setY(startYPositionImage - distanceYToSubtract);

        return true;
    }

    private void initProperties(
            final ImageView child,
            final View dependency) {

        if (!initialised) {
            // form initial layout
            startHeight = child.getHeight();
            startXPositionImage = (int) child.getX();
            startYPositionImage = (int) child.getY();
            startToolbarHeight = dependency.getHeight();
            // some calculated fields
            amountOfToolbarToMove = startToolbarHeight - finalToolbarHeight;
            amountOfImageToReduce = startHeight - finalHeight;
            amountToMoveXPosition = startXPositionImage - finalXPosition;
            amountToMoveYPosition = startYPositionImage - finalYPosition;
            initialised = true;
        }
    }
}

public class AppBarScrollWatcher implements AppBarLayout.OnOffsetChangedListener {
    private int scrollRange = -1;
    private OffsetListener listener;

    public AppBarScrollWatcher(OffsetListener listener) {
        this.listener = listener;
    }

    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        if (scrollRange == -1) {
            scrollRange = appBarLayout.getTotalScrollRange();
        }

        int appbarHeight = scrollRange + verticalOffset;
        float alpha = (float) appbarHeight / scrollRange;
        if (alpha < 0) {
            alpha = 0;
        }
        float alphaZeroOnCollapsed = shrinkAlpha(alpha);
        float alphaZeroOnExpanded = Math.abs(alphaZeroOnCollapsed - 1);
        int argbZeroOnExpanded = (int) Math.abs((alphaZeroOnCollapsed * 255) - 255);
        int argbZeroOnCollapsed = (int) Math.abs(alphaZeroOnCollapsed * 255);

        listener.onAppBarExpanding(alphaZeroOnExpanded <= 0, alphaZeroOnCollapsed <= 0, argbZeroOnExpanded, argbZeroOnCollapsed, alphaZeroOnCollapsed, alphaZeroOnExpanded);
    }

    private float shrinkAlpha(float alpha) {
        NumberFormat formatter = NumberFormat.getInstance(Locale.getDefault());
        formatter.setMaximumFractionDigits(2);
        formatter.setMinimumFractionDigits(2);
        formatter.setRoundingMode(RoundingMode.HALF_DOWN);
        return Float.parseFloat(formatter.format(alpha));
    }

    public interface OffsetListener {
        void onAppBarExpanding(boolean expanded, boolean collapsed, int argbZeroOnExpanded, int argbZeroOnCollapsed, float alphaZeroOnCollapsed, float alphaZeroOnExpanded);
    }
}

res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="AvatarImageBehavior">
        <attr name="finalXPosition" format="dimension" />
        <attr name="finalYPosition" format="dimension" />
        <attr name="finalHeight" format="dimension" />
        <attr name="finalToolbarHeight" format="dimension" />
        <attr name="onlyVerticalMove" format="boolean" />
    </declare-styleable>
</resources>


2. Activity/Fragment中的实现

<?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:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:contentScrim="@color/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:titleEnabled="false">

            <LinearLayout
                android:id="@+id/header_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:gravity="center_horizontal"
                android:orientation="vertical"
                android:paddingStart="24dp"
                android:paddingTop="160dp"
                android:paddingEnd="24dp"
                android:paddingBottom="56dp">

            </LinearLayout>

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?android:attr/actionBarSize"
                android:layout_gravity="bottom"
                app:contentInsetStart="0dp"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:titleMarginStart="0dp">

                <androidx.constraintlayout.widget.ConstraintLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center_vertical">

                    <ImageView
                        android:id="@+id/still_photo"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:layout_gravity="center"
                        android:contentDescription="@string/app_name"
                        android:scaleType="fitCenter"
                        android:visibility="invisible"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent"
                        app:srcCompat="@drawable/ic_ph_person_male_80dp" />

                    <ImageView
                        android:id="@+id/ic_more"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:alpha="0.7"
                        android:clickable="true"
                        android:contentDescription="@string/app_name"
                        android:focusable="true"
                        android:padding="8dp"
                        android:tint="@android:color/white"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintTop_toTopOf="parent"
                        app:srcCompat="@drawable/ic_more_vert_black_24dp" />
                </androidx.constraintlayout.widget.ConstraintLayout>

            </androidx.appcompat.widget.Toolbar>

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

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

    <androidx.core.widget.NestedScrollView
        android:id="@+id/v_sections"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:behavior_overlapTop="24dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <androidx.cardview.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="16dp">

            <View
                android:layout_width="match_parent"
                android:layout_height="1000dp" />
        </androidx.cardview.widget.CardView>
    </androidx.core.widget.NestedScrollView>

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/moving_photo"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_gravity="top|center_horizontal"
        android:layout_marginTop="64dp"
        android:contentDescription="@string/app_name"
        android:scaleType="fitCenter"
        app:finalHeight="48dp"
        app:finalToolbarHeight="?android:attr/actionBarSize"
        app:finalYPosition="4dp"
        app:layout_behavior=".custom.AvatarImageBehavior"
        app:onlyVerticalMove="true"
        app:srcCompat="@drawable/ic_ph_person_male_80dp" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

private lateinit var appBarScrollListener: AppBarScrollWatcher

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_launcher)
        setupAppBar()
    }

    private fun setupAppBar() {
        appBarScrollListener =
            AppBarScrollWatcher(AppBarScrollWatcher.OffsetListener { _, collapsed, _, _, _, _ ->
                still_photo.visibility = if (collapsed) View.VISIBLE else View.INVISIBLE
            })
        app_bar.addOnOffsetChangedListener(appBarScrollListener)
    }

    override fun onDestroy() {
        app_bar.removeOnOffsetChangedListener(appBarScrollListener)
        super.onDestroy()
    }


请注意,您应该在布局中放置两个 ImageView。

  • AppCompatImageView直接在 CoordinatorLayout 里面,这样我们就可以在它上面使用 CoordinatorLayout.Behavior,它就是动态照片。这里的重要道具是app:onlyVerticalMove="true",使您的动态照片垂直滚动。我将默认值设置为 false,它会将照片移动到 CoordinatorLayout 的起点(左上角)。
  • 将另一个 ImageView 放入 Appbar 布局中,作为 Appbar 中显示的最终照片。以不可见状态初始化它,然后在折叠工具栏折叠时使用 AppBarLayout 行为显示照片。

如果Toolbar要从移动元素中排除,只需删除android:layout_gravity="bottom"


推荐阅读