SystemUI 下拉状态栏的motionlayout

SystemUI 下拉状态栏的motionlayout

  • motion 3 这里设置mUseLargeScreenShadeHeader
  • NotificationPanelViewController
  •   public void updateResources() {
          final boolean newSplitShadeEnabled =
                  LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
          final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled;
          mSplitShadeEnabled = newSplitShadeEnabled;
          mQsController.updateResources();
          mNotificationsQSContainerController.updateResources(); //zsg 
          updateKeyguardStatusViewAlignment(/* animate= */false);
          mKeyguardMediaController.refreshMediaPosition();
    
          if (splitShadeChanged) {
              onSplitShadeEnabledChanged();
          }
    
          mSplitShadeFullTransitionDistance =
                  mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
      }
    
  • LargeScreenUtils 中的 config_use_large_screen_shade_header 用来判断是否用motionlayout,
  • 如果是大屏幕就用则没有不开启motionlayout
  •   @JvmStatic
      fun shouldUseLargeScreenShadeHeader(resources: Resources): Boolean {
          return resources.getBoolean(R.bool.config_use_large_screen_shade_header)
      }
    
  • motion 5 如果是用的CombinedQSHeader 则这里不监听触摸事件
    QuickStatusBarHeader
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Only react to touches inside QuickQSPanel
        if (event.getY() > mHeaderQsPanel.getTop()) {
            return super.onTouchEvent(event);
        } else {
            return false;
        }
    }
  • motion 7 这里通过privacyItem是否可见去判断是否让右边的sim卡信息和电池信息显示
  • HeaderPrivacyIconsController.kt
  •   private val picCallback: PrivacyItemController.Callback =
              object : PrivacyItemController.Callback {
          override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) {
              privacyChip.privacyList = privacyItems
              setChipVisibility(privacyItems.isNotEmpty())
          }
    
  • motion 8 这里添加的回调
  •   fun startListening() {
          listening = true
          // Get the most up to date info
          micCameraIndicatorsEnabled = privacyItemController.micCameraAvailable
          locationIndicatorsEnabled = privacyItemController.locationAvailable
          privacyItemController.addCallback(picCallback)
      }
    
  • motion 9 在这里startListening里设置的监听回调,这里的Monitors就是AppOpsPrivacyItemMonitor
    PrivacyItemController
    private fun setListeningState() {
        val listen = callbacks.isNotEmpty()
        if (listening == listen) return
        listening = listen
        if (listening) {
            privacyItemMonitors.forEach { it.startListening(privacyItemMonitorCallback) }
            update()
        } else {
            privacyItemMonitors.forEach { it.stopListening() }
            // Make sure that we remove all indicators and notify listeners if we are not
            // listening anymore due to indicators being disabled
            update()
        }
    }
  • motion 10 在这里添加回调appOpsCallback,有打开camera等就回调
    AppOpsPrivacyItemMonitor
    @GuardedBy("lock")
    private fun setListeningStateLocked() {
        val shouldListen = callback != null && (micCameraAvailable || locationAvailable)
        if (listening == shouldListen) {
            return
        }

        listening = shouldListen
        if (shouldListen) {
            appOpsController.addCallback(OPS, appOpsCallback)
            userTracker.addCallback(userTrackerCallback, bgExecutor)
            onCurrentProfilesChanged()
        } else {
            appOpsController.removeCallback(OPS, appOpsCallback)
            userTracker.removeCallback(userTrackerCallback)
        }
    }

A14 motionLayout in SystemUI

请添加图片描述

combined_qs_header.xml
MotionLayout 1 在这里设置的Transition 布局

<com.android.systemui.util.NoRemeasureMotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/split_shade_status_bar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/qs_header_height"
    android:minHeight="@dimen/large_screen_shade_header_min_height"
    android:clickable="false"
    android:focusable="true"
    android:paddingLeft="@dimen/qs_panel_padding"
    android:paddingRight="@dimen/qs_panel_padding"
    android:visibility="gone"
    android:theme="@style/Theme.SystemUI.QuickSettings.Header"
    app:layoutDescription="@xml/combined_qs_header_scene"><!--zsg here-->

combined_qs_header_scene.xml
MotionLayout 2 注意这里有两个transition,一个header_transition,一个large_screen_header_transition.
在每一个transition中可以设置开始和结束时的两种布局,以及用KeyFrameSet描述过渡动画。

MotionLayout 3 qqs_header 是第一个transition的开始界面布局,qs_header是第一个的结束界面布局

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2021 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<MotionScene
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:motion="http://schemas.android.com/apk/res-auto">
    <!--zsg here 2-->
    <Transition
        android:id="@+id/header_transition"
        app:constraintSetEnd="@id/qs_header_constraint"
        app:constraintSetStart="@id/qqs_header_constraint"
        motion:layoutDuringTransition="honorRequest">
        <KeyFrameSet>
            <!-- These positions are to prevent visual movement of @id/date -->
            <KeyPosition
                app:keyPositionType="deltaRelative"
                app:percentX="0"
                app:percentY="@dimen/percent_displacement_at_fade_out"
                app:framePosition="@integer/fade_out_complete_frame"
                app:sizePercent="0"
                app:curveFit="linear"
                app:motionTarget="@id/date" />
            <KeyPosition
                app:keyPositionType="deltaRelative"
                app:percentX="1"
                app:percentY="0.5"
                app:sizePercent="1"
                app:framePosition="50"
                app:curveFit="linear"
                app:motionTarget="@id/date" />
            <KeyAttribute
                app:motionTarget="@id/date"
                app:framePosition="14"
                android:alpha="0"
                />
            <KeyAttribute
                app:motionTarget="@id/date"
                app:framePosition="@integer/fade_in_start_frame"
                android:alpha="0"
                />
            <KeyPosition
                app:keyPositionType="deltaRelative"
                app:percentX="0"
                app:percentY="@dimen/percent_displacement_at_fade_out"
                app:framePosition="@integer/fade_out_complete_frame"
                app:sizePercent="0"
                app:curveFit="linear"
                app:motionTarget="@id/statusIcons" />
            <KeyPosition
                app:keyPositionType="deltaRelative"
                app:percentX="1"
                app:percentY="0.5"
                app:framePosition="50"
                app:sizePercent="1"
                app:curveFit="linear"
                app:motionTarget="@id/statusIcons" />
            <KeyAttribute
                app:motionTarget="@id/statusIcons"
                app:framePosition="@integer/fade_out_complete_frame"
                android:alpha="0"
                />
            <KeyAttribute
                app:motionTarget="@id/statusIcons"
                app:framePosition="@integer/fade_in_start_frame"
                android:alpha="0"
                />
            <KeyPosition
                app:keyPositionType="deltaRelative"
                app:percentX="0"
                app:percentY="@dimen/percent_displacement_at_fade_out"
                app:framePosition="@integer/fade_out_complete_frame"
                app:percentWidth="1"
                app:percentHeight="1"
                app:curveFit="linear"
                app:motionTarget="@id/batteryRemainingIcon" />
            <KeyPosition
                app:keyPositionType="deltaRelative"
                app:percentX="1"
                app:percentY="0.5"
                app:framePosition="50"
                app:percentWidth="1"
                app:percentHeight="1"
                app:curveFit="linear"
                app:motionTarget="@id/batteryRemainingIcon" />
            <KeyAttribute
                app:motionTarget="@id/batteryRemainingIcon"
                app:framePosition="@integer/fade_out_complete_frame"
                android:alpha="0"
                />
            <KeyAttribute
                app:motionTarget="@id/batteryRemainingIcon"
                app:framePosition="@integer/fade_in_start_frame"
                android:alpha="0"
                />
            <KeyPosition
                app:motionTarget="@id/carrier_group"
                app:percentX="1"
                app:percentY="0.5"
                app:framePosition="50"
                app:percentWidth="1"
                app:percentHeight="1"
                app:curveFit="linear"
                app:keyPositionType="deltaRelative" />
            <KeyAttribute
                app:motionTarget="@id/carrier_group"
                app:framePosition="0"
                android:alpha="0" />
            <KeyAttribute
                app:motionTarget="@id/carrier_group"
                app:framePosition="@integer/fade_in_start_frame"
                android:alpha="0" />
        </KeyFrameSet>
    </Transition>

    <Transition
        android:id="@+id/large_screen_header_transition"
        app:constraintSetStart="@id/large_screen_header_constraint"
        app:constraintSetEnd="@id/large_screen_header_constraint"/>

    <Include app:constraintSet="@xml/large_screen_shade_header"/>

    <Include app:constraintSet="@xml/qs_header"/>

    <Include app:constraintSet="@xml/qqs_header"/><!--zsg here 3-->
</MotionScene>

ShadeModule.kt
MotionLayout 4 在这里通过dagger 依赖注入给ShadeHeaderViewControler

        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
        @Provides
        @SysUISingleton
        @Named(SHADE_HEADER)
        fun providesShadeHeaderView(
            notificationShadeWindowView: NotificationShadeWindowView,
        ): MotionLayout {
            val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
            val layoutId = R.layout.combined_qs_header
            stub.layoutResource = layoutId
            return stub.inflate() as MotionLayout
        }

status_bar_expanded.xml
MotionLayout 5 qs_header_stub 在这个布局里,它的最外层view是NotificationPanelView

        <!-- This view should be after qs_frame so touches are dispatched first to it. That gives
             it a chance to capture clicks before the NonInterceptingScrollView disallows all
             intercepts -->
        <ViewStub
            android:id="@+id/qs_header_stub"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
        />

NotificationPanelViewController.java
MotionLayout 6 shadeHeaderController 在这里注入,并在下面init

    @Inject
    public NotificationPanelViewController(NotificationPanelView view,
            @Main Handler handler,
            LayoutInflater layoutInflater,
        ..........................................
        ..........................................
        ..........................................
        mView.setWillNotDraw(!DEBUG_DRAWABLE);
        mShadeHeaderController = shadeHeaderController;//zsg here
        mLayoutInflater = layoutInflater;

ShadeHeaderController.kt
MotionLayout 7 在这里注入view ,@Named(SHADE_HEADER) 用来区分相同的属性名 不同对象的情况

@SysUISingleton
class ShadeHeaderController
@Inject
constructor(
    @Named(SHADE_HEADER) private val header: MotionLayout, //zsg 

ShadeHeaderController.kt
MotionLayout 8 在这里通过largeScreenActive判断是用哪个transition

    private fun updateTransition() {
        if (largeScreenActive) {
            logInstantEvent("Large screen constraints set")
            header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
        } else {
            logInstantEvent("Small screen constraints set")
            header.setTransition(HEADER_TRANSITION_ID)
        }
        header.jumpToState(header.startState)
        updatePosition()
        updateScrollY()
    }

QuickSettingsController.java
MotionLayout 9 在这里监听的滑动,并且判断是否展开到Qs

    private void onScroll(int scrollY) {
        mShadeHeaderController.setQsScrollY(scrollY);
        if (scrollY > 0 && !mFullyExpanded) {
            // TODO (b/265193930): remove dependency on NPVC
            // If we are scrolling QS, we should be fully expanded.
            mPanelViewControllerLazy.get().expandToQs();
        }
    }

ShadeHeaderController.kt
MotionLayout 10 这里就是让motionlayout动起来的地方,设置header.progress

    private fun updatePosition() {
        if (!largeScreenActive && visible) {
            logInstantEvent("updatePosition: $qsExpandedFraction")
            header.progress = qsExpandedFraction
            updateBatteryMode()
        }
    }

ShadeCarrierGroupController.java
MotionTwo 1 这里是控制wwan设备显示 无SIM卡 ,WLAN设备上carrierText是空就不显示

    @MainThread
    private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
        if (!mMainHandler.getLooper().isCurrentThread()) {
            mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
            return;
        }

        mNoSimTextView.setVisibility(View.GONE);
        if (!info.airplaneMode && info.anySimReady) {
            boolean[] slotSeen = new boolean[SIM_SLOTS];
            if (info.listOfCarriers.length == info.subscriptionIds.length) {
                for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
                    int slot = getSlotIndex(info.subscriptionIds[i]);
                    if (slot >= SIM_SLOTS) {
                        Log.w(TAG, "updateInfoCarrier - slot: " + slot);
                        continue;
                    }
                    if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
                        Log.e(TAG,
                                "Invalid SIM slot index for subscription: "
                                        + info.subscriptionIds[i]);
                        continue;
                    }
                    String carrierText = info.listOfCarriers[i].toString().trim();
                    if (!TextUtils.isEmpty(carrierText)) {
                        mInfos[slot] = mInfos[slot].changeVisibility(true);
                        slotSeen[slot] = true;
                        mCarrierGroups[slot].setCarrierText(carrierText);
                        mCarrierGroups[slot].setVisibility(View.VISIBLE);
                    }
                }
                for (int i = 0; i < SIM_SLOTS; i++) {
                    if (!slotSeen[i]) {
                        mInfos[i] = mInfos[i].changeVisibility(false);
                        mCarrierGroups[i].setVisibility(View.GONE);
                    }
                }
            } else {
                Log.e(TAG, "Carrier information arrays not of same length");
            }
        } else {
            // No sims or airplane mode (but not WFC). Do not show ShadeCarrierGroup,
            // instead just show info.carrierText in a different view.
            for (int i = 0; i < SIM_SLOTS; i++) {
                mInfos[i] = mInfos[i].changeVisibility(false);
                mCarrierGroups[i].setCarrierText("");
                mCarrierGroups[i].setVisibility(View.GONE);
            }
            mNoSimTextView.setText(info.carrierText);
            if (!TextUtils.isEmpty(info.carrierText)) { //zsg here
                mNoSimTextView.setVisibility(View.VISIBLE);
                mNoSimTextView.setTextColor(Color.YELLOW);
                Log.i(TAG, "info.carrierText: "+info.carrierText);
            }
        }
        handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
    }

HeaderPrivacyIconsController.kt
MotionTwo 2 这里控制camera绿色图标的显示,下面onChipVisibilityRefreshed控制当camera图标显示时,原来的其他图标是否显示

    private fun setChipVisibility(visible: Boolean) {
        if (visible && getChipEnabled()) {
            privacyLogger.logChipVisible(true)
            // Makes sure that the chip is logged as viewed at most once each time QS is opened
            // mListening makes sure that the callback didn't return after the user closed QS
            if (!privacyChipLogged && listening) {
                privacyChipLogged = true
                uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW)
            }
        } else {
            privacyLogger.logChipVisible(false)
        }

        privacyChip.visibility = if (visible) View.VISIBLE else View.GONE
        chipVisibilityListener?.onChipVisibilityRefreshed(visible)
    }

CombinedShadeHeadersConstraintManager.kt
MotionTwo 3 这里是用上面typealias定义的一个高阶函数,
方法体里是定义了这个数据类可以直接用+号合并,
所以之前其实就是构造了一个ConstraintsChanges ,传入了一个设置ConstraintSet的高阶函数

/**
 * Contains all changes that need to be performed to the different [ConstraintSet] in
 * [ShadeHeaderController].
 */
data class ConstraintsChanges(
    val qqsConstraintsChanges: ConstraintChange? = null,
    val qsConstraintsChanges: ConstraintChange? = null,
    val largeScreenConstraintsChanges: ConstraintChange? = null
) {
    operator fun plus(other: ConstraintsChanges) = ConstraintsChanges(
        qqsConstraintsChanges + other.qqsConstraintsChanges,
        qsConstraintsChanges + other.qsConstraintsChanges,
        largeScreenConstraintsChanges + other.largeScreenConstraintsChanges
    )
}

ShadeHeaderController.kt
MotionTwo 4 这里是一个扩展函数

    private val chipVisibilityListener: ChipVisibilityListener =
        object : ChipVisibilityListener {
            override fun onChipVisibilityRefreshed(visible: Boolean) {
                // If the privacy chip is visible, we hide the status icons and battery remaining
                // icon, only in QQS.
                val update =
                    combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(visible)
                header.updateAllConstraints(update)//zsg here
            }
        }

ShadeHeaderController.kt
MotionTwo 5 扩展函数,然后调用下面的扩展函数updateConstraints
MotionTwo 6 最后调用更新MotionLayout




private fun MotionLayout.updateConstraints(@IdRes state: Int, update: ConstraintChange) {//zsg 6
    val constraints = getConstraintSet(state)
    constraints.update()
    updateState(state, constraints)
}

    /**
     * Updates the [ConstraintSet] for the case of combined headers.
     *
     * Only non-`null` changes are applied to reduce the number of rebuilding in the [MotionLayout].
     */
    private fun MotionLayout.updateAllConstraints(updates: ConstraintsChanges) {
        if (updates.qqsConstraintsChanges != null) {
            updateConstraints(QQS_HEADER_CONSTRAINT, updates.qqsConstraintsChanges)
        }
        if (updates.qsConstraintsChanges != null) {
            updateConstraints(QS_HEADER_CONSTRAINT, updates.qsConstraintsChanges)
        }
        if (updates.largeScreenConstraintsChanges != null) {
            updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
        }
    }

二、一个motionLayout的demo
https://developer.android.google.cn/develop/ui/views/animations/motionlayout/examples
请添加图片描述
请添加图片描述

CollapsingToolbar.kt

        //zsg AppBarLayout 在这里监听关联的view的滑动
        // When the AppBarLayout progress changes, snap MotionLayout to the current progress
        val listener = AppBarLayout.OnOffsetChangedListener { appBar, verticalOffset ->
            // convert offset into % scrolled
            val seekPosition = -verticalOffset / appBar.totalScrollRange.toFloat()
            // inform both both MotionLayout and CutoutImage of the animation progress.
            binding.motionLayout.progress = seekPosition//zsg 设置motionLayout的进度
            binding.background.translationProgress = (100 * seekPosition).toInt()
        }

activity_collapsing_toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2020 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- Use a CoordinatorLayout and AppBar to build a Collapsing Toolbar from MotionLayout -->
<!-- See: https://developer.android.com/reference/androidx/coordinatorlayout/widget/CoordinatorLayout -->
<!-- See: https://developer.android.com/reference/com/google/android/material/appbar/AppBarLayout-->
<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">

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <include layout="@layout/lots_of_cards" />
    </androidx.core.widget.NestedScrollView>

    <!--AppBarLayout is a vertical LinearLayout which implements many of the features of material designs app bar concept, namely scrolling gestures.

Children should provide their desired scrolling behavior through AppBarLayout.LayoutParams.setScrollFlags(int) and the associated layout xml attribute: app:layout_scrollFlags.

This view depends heavily on being used as a direct child within a CoordinatorLayout. If you use AppBarLayout within a different ViewGroup, most of its functionality will not work.

AppBarLayout also requires a separate scrolling sibling in order to know when to scroll. The binding is done through the AppBarLayout.ScrollingViewBehavior behavior class, meaning that you should set your scrolling view's behavior to be an instance of AppBarLayout.ScrollingViewBehavior. A string resource containing the full class name is available.-->
<!--zsg AppBarLayout 一般只在CoordinatorLayout 里使用,它会和设置layout_behavior 的同级可滑动view关联上-->
    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appbar_layout"
        android:background="#00000000"
        android:layout_width="match_parent"
        android:layout_height="200dp">
        <!--zsg 这里设置trasition  activity_collapsing_toolbar_scene-->
        <androidx.constraintlayout.motion.widget.MotionLayout
            android:id="@+id/motion_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:minHeight="100dp"
            app:layoutDescription="@xml/activity_collapsing_toolbar_scene"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <com.example.androidstudio.motionlayoutintegrations.CutoutImage
                android:id="@+id/background"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:scaleType="centerCrop"
                android:src="@drawable/star_parallax"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="@id/expanded_bottom"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:bottomCut="120dp"
                app:cutoutColor="@color/white"
                app:layout_constraintVertical_bias="0.52" />

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="50dp"
                android:layout_marginBottom="16dp"
                android:gravity="center_vertical"
                android:textColor="@color/white"
                android:text="@string/ringed_planet"
                android:textAppearance="?attr/textAppearanceHeadline5"
                app:layout_constraintBottom_toTopOf="@id/icon"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.24000001" />

            <TextView
                android:id="@+id/subtitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:textColor="@color/white"
                android:text="@string/added_in_2019_in_unicode_12_0"
                android:textAppearance="?attr/textAppearanceBody2"
                android:visibility="invisible"
                app:layout_constraintStart_toStartOf="@id/title"
                app:layout_constraintTop_toBottomOf="@id/title" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/collapsed_top"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                app:layout_constraintGuide_end="100dp"
                />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/expanded_bottom"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_constraintGuide_end="60dp"
                android:orientation="horizontal"
                />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/inset"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                app:layout_constraintGuide_begin="0dp"
                />

            <TextView
                android:id="@+id/icon"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:gravity="center"
                android:text="@string/ringed_planet_emoji"
                app:autoSizeTextType="uniform" />

            <View
                android:id="@+id/systembar_overlay"
                android:layout_width="0dp"
                android:layout_height="100dp"
                android:background="#0E328F"
                app:layout_constraintTop_toTopOf="@id/collapsed_top"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                android:alpha="0.5"
                />

        </androidx.constraintlayout.motion.widget.MotionLayout>
    </com.google.android.material.appbar.AppBarLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

activity_collapsing_toolbar_scene.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2020 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <Transition
        app:constraintSetEnd="@+id/collapsed"
        app:constraintSetStart="@id/expanded"
        >
        <!--zsg KeyPosition 是view在对应某个时刻的位置 framePosition="25" 表示当到达整个过程的25%时-->
        <!--zsg KeyAttribute是view在对应某个时刻的大小形状-->
        <!--zsg percentX percentY 的方向是从起始位置到结束位置,距离就是设置的分数 1表示到达最后的位置处 0 表示不动-->
        <KeyFrameSet >
            <!-- Hold title in place -->
            <KeyPosition
                app:motionTarget="@+id/title"
                app:framePosition="25"
                app:percentX="0.0"
                app:percentY=".2" />
            <!-- At 55, title should still be in place -->
            <KeyPosition
                app:motionTarget="@+id/title"
                app:framePosition="55"
                app:percentX="0" />
            <!-- at 75, title is 50% to destination -->
            <KeyPosition
                app:motionTarget="@+id/title"
                app:framePosition="75"
                app:percentX=".5" />
            <!-- Hide subtitle until 55 -->
            <KeyAttribute
                app:motionTarget="@+id/subtitle"
                app:framePosition="55"
                android:alpha="0" />
            <!-- Hold subtitle in place until 55 -->
            <KeyPosition
                app:motionTarget="@+id/subtitle"
                app:framePosition="55"
                app:percentX="0" />
            <!-- at 55, subtitle is 50% to destination -->
            <KeyPosition
                app:motionTarget="@+id/subtitle"
                app:framePosition="75"
                app:percentX=".5" />
            <!-- "pop" icon out at 35, with some translation to make it visually stay in place -->
            <KeyAttribute
                app:motionTarget="@+id/icon"
                app:framePosition="35"
                android:scaleX="0"
                android:scaleY="0"
                android:translationY="30dp"
                android:translationX="0dp"
                app:curveFit="linear" />
            <!-- Don't move icon along curve until 35 -->
            <KeyPosition
                app:motionTarget="@+id/icon"
                app:framePosition="35"
                app:percentX="0"
                app:percentY="0"
                app:curveFit="linear" />
            <!-- Between 35 and 36 while icon is not visible, jump icon to it's final location -->
            <KeyPosition
                app:motionTarget="@+id/icon"
                app:framePosition="36"
                app:percentX="1"
                app:percentY="1"
                app:curveFit="linear" />
            <!-- Translate icon a bit to make it "fill in" after jump -->
            <KeyAttribute
                app:motionTarget="@+id/icon"
                app:framePosition="36"
                android:translationY="-30dp"
                android:translationX="20dp"
                app:curveFit="linear" />
            <!-- Animate the bottomCutSize to 0 -->
            <KeyAttribute
                app:motionTarget="@id/background"
                app:framePosition="60">
                <CustomAttribute app:attributeName="bottomCutSize" app:customDimension="0dp" />
            </KeyAttribute>
            <!-- overshoot endCutSize -->
            <KeyAttribute
                app:motionTarget="@id/background"
                app:framePosition="80">
                <CustomAttribute app:attributeName="endCutSize" app:customDimension="220dp" />
            </KeyAttribute>
            <!-- bring the system bar overlay in slightly early (numbers decided visually) -->
            <KeyPosition
                app:motionTarget="@+id/systembar_overlay"
                app:framePosition="45"
                app:percentY=".50"
                app:sizePercent="0.50" />
        </KeyFrameSet>
    </Transition>

    <!-- Start constraint set uses expanded_bottom and parent as bottom and top bounds -->
    <ConstraintSet android:id="@+id/expanded">
        <Constraint
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="@id/parent"
            app:layout_constraintBottom_toTopOf="@id/icon"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginBottom="16dp"
            android:layout_marginTop="0dp"
            />
        <Constraint
            android:id="@+id/subtitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/title"
            app:layout_constraintStart_toStartOf="@id/title"
            android:visibility="invisible"
            android:layout_marginTop="8dp"
            android:alpha="0" />
        <Constraint
            android:id="@+id/icon"
            app:layout_constraintTop_toTopOf="@id/expanded_bottom"
            app:layout_constraintBottom_toTopOf="@id/expanded_bottom"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_width="100dp"
            android:layout_height="100dp"
            />
        <Constraint
            android:id="@+id/background"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/expanded_bottom"
            app:layout_constraintStart_toStartOf="parent">
            <CustomAttribute app:attributeName="bottomCutSize" app:customDimension="120dp" />
            <CustomAttribute app:attributeName="endCutSize" app:customDimension="0dp" />
        </Constraint>
        <Constraint
            android:id="@id/systembar_overlay"
            android:layout_height="0dp"
            android:layout_width="0dp"
            android:alpha="0.5"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />
    </ConstraintSet>

    <!-- end constraint set uses collapsed_top and parent as top and bottom, with inset as
         guideline -->
    <ConstraintSet android:id="@+id/collapsed" >
        <Constraint
            android:id="@+id/title"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:layout_marginBottom="0dp"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            app:layout_constraintTop_toTopOf="@+id/inset"
            app:layout_constraintBottom_toTopOf="@id/subtitle"
            app:layout_constraintStart_toStartOf="parent"
            />
        <Constraint
            android:id="@+id/subtitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="visible"
            app:layout_constraintTop_toBottomOf="@id/title"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginLeft="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginBottom="0dp"
            android:layout_marginTop="0dp" />
        <Constraint
            android:id="@+id/icon"
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="@id/inset"
            android:layout_marginEnd="16dp" />
        <Constraint
            android:id="@+id/background"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="@id/collapsed_top"
            app:layout_constraintStart_toStartOf="parent">
            <CustomAttribute app:attributeName="bottomCutSize" app:customDimension="0dp" />
            <CustomAttribute app:attributeName="endCutSize" app:customDimension="180dp" />
        </Constraint>
        <Constraint
            android:id="@id/systembar_overlay"
            android:layout_height="0dp"
            android:layout_width="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/inset"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:alpha="0.5"
            />
    </ConstraintSet>
</MotionScene>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值