Material component/ transition 1. Button 圓角選項卡的實現方法 MaterialContainerTransform Motion

1. Button

https://material.io/develop/android/components/buttons

原來Button設置不同的style就可以有不同的效果啊,以前一直都自定義的。



1.1 Text button

<Button
    android:id="@+id/textButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Text button"
    style="@style/Widget.MaterialComponents.Button.TextButton"
/>

還可以加Icon,之前以爲只有MaterialButton裏才支持這個

    <Button
            <Button
        app:icon="@drawable/ic_volume"
        app:iconSize="30dp"
        app:iconTint="@color/colorAccent"
        app:iconGravity="textStart"
        app:iconPadding="5dp"
        style="@style/Widget.MaterialComponents.Button.TextButton.Icon"

2.2 Outlined button
are medium-emphasis buttons. They contain actions that are important, but aren’t the primary action in an app.

style="@style/Widget.MaterialComponents.Button.OutlinedButton"

//or

style="?attr/materialButtonOutlinedStyle"

3.3 Contained button
默認的就這種,背景是colorPrimary顏色

3.4 Toggle button
帶狀態切換的,需要一個容器


<com.google.android.material.button.MaterialButtonToggleGroup
    android:id="@+id/toggleButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        style="?attr/materialButtonOutlinedStyle"
    />
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2"
        style="?attr/materialButtonOutlinedStyle"
    />
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 3"
        style="?attr/materialButtonOutlinedStyle"
    />
</com.google.android.material.button.MaterialButtonToggleGroup>

圓角選項卡的實現方法

1.1 使用button

    <com.google.android.material.button.MaterialButtonToggleGroup
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_margin="20dp"
        app:checkedButton="@id/btn_1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar"
        app:selectionRequired="true"
        app:singleSelection="true">

        <com.google.android.material.button.MaterialButton
            android:id="@+id/btn_1"
            style="@style/TabSingleCheckedForSettings"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="car"
            app:icon="@drawable/ic_vpi_car_d"
            app:shapeAppearanceOverlay="@style/TabLeftShapeRoundCornerTop" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/btn_2"
            style="@style/TabSingleCheckedForSettings"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="suv"
            app:icon="@drawable/ic_vpi_suv_d" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/btn_3"
            style="@style/TabSingleCheckedForSettings"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="towing"
            app:icon="@drawable/ic_vpi_towing_d" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/btn_4"
            style="@style/TabSingleCheckedForSettings"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="truck"
            app:icon="@drawable/ic_vpi_truck_d"
            app:shapeAppearanceOverlay="@style/TabRightShapeRoundCornerTop" />
    </com.google.android.material.button.MaterialButtonToggleGroup>

用到的style

    <style name="TabSingleCheckedForSettings">
        <item name="strokeColor">@color/colorPrimary2</item>
        <item name="strokeWidth">@dimen/mtrl_btn_stroke_size</item>
        <item name="backgroundTint">@color/select_trans_primary</item>
        <item name="android:textColor">@color/select_tab_text_color</item>
        <item name="iconTint">@color/select_tab_text_color</item>
        <item name="iconSize">25dp</item>
        <item name="iconGravity">textStart</item>
        <item name="iconPadding">5dp</item>
        <item name="android:textSize">26sp</item>
        <item name="android:insetTop">0dp</item>
        <item name="android:insetBottom">0dp</item>
        <item name="android:stateListAnimator">@null</item>
    </style>

    <style name="TabLeftShapeRoundCornerTop">
        <item name="cornerSizeTopLeft">50%</item>
        <item name="cornerSizeBottomLeft">50%</item>
    </style>
    <style name="TabRightShapeRoundCornerTop">
        <item name="cornerSizeTopRight">50%</item>
        <item name="cornerSizeBottomRight">50%</item>
    </style>

用到的colors
select_trans_primary.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/colorPrimary2" android:state_checked="true" />
    <item android:color="@color/transparent" android:state_checked="false"/>
</selector>

文本,icon顏色 select_tab_text_color.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/content_black_color" android:state_checked="true"/>
    <item android:color="@color/colorPrimary2" android:state_checked="false"/>
</selector>

1.2 使用TabLayout ,外邊套個CardView來處理圓角,分割線用divider

不過了TabLayout 只有文字上的icon,沒有左邊的,我們可能要拿到textview設置drawableLeft了?
或者用自定義view來設置tab

MaterialContainerTransform

A shared element {@link Transition} that transforms one container to another.
MaterialContainerTransform can be used to morph between two Activities, Fragments, Views or a View to a Fragment.

public final class MaterialContainerTransform extends Transition {

//指出是入場還是出廠動畫
  @IntDef({TRANSITION_DIRECTION_AUTO, TRANSITION_DIRECTION_ENTER, TRANSITION_DIRECTION_RETURN})
  @Retention(RetentionPolicy.SOURCE)
  public @interface TransitionDirection {}

//淡入淡出模式
  @IntDef({FADE_MODE_IN, FADE_MODE_OUT, FADE_MODE_CROSS, FADE_MODE_THROUGH})
  @Retention(RetentionPolicy.SOURCE)
  public @interface FadeMode {}

//匹配寬或者高
  @IntDef({FIT_MODE_AUTO, FIT_MODE_WIDTH, FIT_MODE_HEIGHT})
  @Retention(RetentionPolicy.SOURCE)
  public @interface FitMode {}

1.1 在兩個fragment之間進行轉場動畫
①首先在共享view加上一樣的transitionName名字,比如

<!--fragment_a.xml-->
<View
  android:id="@+id/start_view"
  android:transitionName="shared_element_container" />

<!--fragment_b.xml-->
<View
  android:id="@+id/end_view"
  android:transitionName="shared_element_container" />

②給即將跳轉的FragmentB 設置sharedElementEnterTransition ,可以在new出來的fragment b對象上設置,也可以直接在Fragment b裏設置,如下兩種都行.

// FragmentA.kt
val fragmentB = FragmentB()
fragmentB.sharedElementEnterTransition = MaterialContainerTransform()


/*** OR ***/


// FragmentB.kt
override fun onCreate(savedInstanceState: Bundle?)  {
  super.onCreate(savedInstanceState)
  sharedElementEnterTransition = MaterialContainerTransform()
}

③設置共享元素


childFragmentManager
  .beginTransaction()
  // Map the start View in FragmentA and the transitionName of the end View in FragmentB
  .addSharedElement(view, "shared_element_container")
  .replace(R.id.fragment_container, fragmentB, FragmentB.TAG)
  .addToBackStack(FragmentB.TAG)
  .commit()

方法源碼註解

    /**
     * Used with custom Transitions to map a View from a removed or hidden
     * Fragment to a View from a shown or added Fragment.
     * <var>sharedElement</var> must have a unique transitionName in the View hierarchy.
     *
     * @param sharedElement A View in a disappearing Fragment to match with a View in an
     *                      appearing Fragment.
     * @param name The transitionName for a View in an appearing Fragment to match to the shared
     *             element.
     * @see Fragment#setSharedElementReturnTransition(Object)
     * @see Fragment#setSharedElementEnterTransition(Object)
     */
    @NonNull
    public FragmentTransaction addSharedElement(@NonNull View sharedElement, @NonNull String name) 

然後測試結果還湊合,不過嘗試修改FragmentB裏sharedElementEnterTransition的duration時間,改長點,比如2秒,然後就發現,從A到B變化的時候,會先出現一個淺灰色的背景,然後動畫在這個灰色的背景上發生,看着效果不太好,後退的時候動畫沒啥問題.
1.2 activity 之間跳轉動畫
Note: Activity and Window transitions require using Android Framework Transitions provided in the com.google.android.material.transition.platform package and are only available on API level 21 and above.
transition目錄下的類ABC等,在platform下都有同名的ABC,這裏意思是用platform下的


另外對於viedeoview,surfaceView之類的,不要用這個跳轉,效果不行還可能出現異常.因爲他們都不在ui線程更新的,有自己的線程處理,會出問題.

使用步驟,假設從A跳到B

① AB 都需要 activity transition feature,可以在代碼裏設置,也可以在style裏設置.

override fun onCreate(savedInstanceState: Bundle?) {
  // Enable Activity Transitions. Optionally enable Activity transitions in your
  // theme with <item name=”android:windowActivityTransitions”>true</item>.
  window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)

另外A 需要如下設置,也是在onCreate裏,跟在上邊的requestFeature下面即可

  // Attach a callback used to capture the shared elements from this Activity to be used
  // by the container transform transition
  setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())

  // Keep system bars (status bar, navigation bar) persistent throughout the transition.
  window.sharedElementsUseOverlay = false

② A 跳轉代碼

sharedView.setTransitionName("xxxx")//在A裏是唯一的就行
        val options = ActivityOptions.makeSceneTransitionAnimation(
            this,
            sharedView,//A 裏要進行動畫的view
            "shared_element_container" // The transition name to be matched in Activity B.
        )
        startActivity(Intent(this, B.class),options.toBundle())

③ B裏的設置
下邊默認的是用的B整個佈局做爲跳轉對象的,你也可以用B裏邊的某些view或viewgroup來作爲跳轉對象,

override fun onCreate(savedInstanceState: Bundle?) {

  // Enable Activity Transitions. Optionally enable Activity transitions in your
  // theme with <item name=”android:windowActivityTransitions”>true</item>.
  window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)

  // Set the transition name, which matches Activity A’s start view transition name, on
  // the root view.
  findViewById<View>(android.R.id.content).transitionName = "shared_element_container"

  // Attach a callback used to receive the shared elements from Activity A to be
  // used by the container transform transition.
  setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())

  // Set this Activity’s enter and return transition to a MaterialContainerTransform
  window.sharedElementEnterTransition = MaterialContainerTransform().apply {
    addTarget(android.R.id.content)
    duration = 300L
setAllContainerColors(Color.WHITE)//activity B 可能沒有設置背景,跳轉的時候有可能看到底層,不太好看
  }
  window.sharedElementReturnTransition = MaterialContainerTransform().apply {
    addTarget(android.R.id.content)
    duration = 250L
setAllContainerColors(Color.WHITE)// startView 可能沒有背景,透明的不太好看,可以加個這個
  }

  super.onCreate(bundle)
  setContentView(R.layout.activity_b)
  ...
}

1.3 view 之間的轉換
試了下,不知道啥用,關鍵是動畫前啥樣子,動畫結束後又還原了,比如demo裏,startView從可見到不可見,endView相反,可動畫結束又成老樣子了.
我把代碼複製到別的demo是正常的,後來發現這裏出問題是因爲容器是MotionLayout,不知道這個爲啥會影響這個,可能motionlayout自己就能管理動畫吧

val transform = MaterialContainerTransform().apply {
  // Manually tell the container transform which Views to transform between.
  startView = fab
  endView = bottomToolbar

  // Ensure the container transform only runs on a single target
  addTarget(endView)

  // Optionally add a curved path to the transform
  pathMotion = MaterialArcMotion()

  // Since View to View transforms often are not transforming into full screens,
  // remove the transition's scrim.
  scrimColor = Color.TRANSPARENT
}

// Begin the transition by changing properties on the start and end views or
// removing/adding them from the hierarchy.
TransitionManager.beginDelayedTransition(container, transform)
fab.visibility = View.GONE
bottomToolbar.visibility = View.VISIBLE

Motion

  1. transition
    相關的一些類學習,至於啥用再說
    1.1 VisibilityAnimatorProvider
    接口名字基本就說明了它是幹啥的了,爲可見性提供動畫的,出現和消失的animator
public interface VisibilityAnimatorProvider {

  /**
   * Should return an Animator that animates in the appearing target {@code view}.
   *
   * @param sceneRoot The root of the transition hierarchy, which can be useful for checking
   *     configurations such as RTL
   * @param view The view that is appearing
   */
  @Nullable
  Animator createAppear(@NonNull ViewGroup sceneRoot, @NonNull View view);

  /**
   * Should return an Animator that animates out the disappearing target {@code view}.
   *
   * @param sceneRoot The root of the transition hierarchy, which can be useful for checking
   *     configurations such as RTL
   * @param view The view that is disappearing
   */
  @Nullable
  Animator createDisappear(@NonNull ViewGroup sceneRoot, @NonNull View view);
}

系統有4個實現類
FadeProvider
就是透明度的變化,默認appear是從0到incomingEndThreshold【defautlvalue是1,可修改】,disappear是從1到0不可修改

/** A class that configures and is able to provide an {@link Animator} that fades a view. */
public final class FadeProvider implements VisibilityAnimatorProvider {


}

FadeThroughProvider
和上邊的差不多,不過這個有個固定的閥值,透明度是從0.35到1 ,從1到0.35

/**
 * A class that configures and is able to provide an {@link Animator} that fades out or in a view.
 *
 * <p>FadeThroughProvider differs from FadeProvider in that it fades out and in views sequentially.
 */
public final class FadeThroughProvider implements VisibilityAnimatorProvider {

  static final float PROGRESS_THRESHOLD = 0.35f;

}

ScaleProvider
就是提供拉伸動畫,出現的時候是從0.8到1 ,消失的時候是從1到1.1

public final class ScaleProvider implements VisibilityAnimatorProvider {

  private float outgoingStartScale = 1f;
  private float outgoingEndScale = 1.1f;
  private float incomingStartScale = 0.8f;
  private float incomingEndScale = 1f;

SlideDistanceProvider
這個是平移動畫,構造方法裏需要一個Gravity的參數,指明最終停留的邊界,當然是從另一邊移動過來的,
上下左右,還可以設置移動距離,如果不設置有個默認的距離30dp

/**
 * A class that can configure and create an {@link Animator} that slides a view vertically or
 * horizontally slide over a specific distance.
 */
public final class SlideDistanceProvider implements VisibilityAnimatorProvider {

public SlideDistanceProvider(@GravityFlag int slideEdge) {
    this.slideEdge = slideEdge;
  }

  private static Animator createTranslationAppearAnimator(
      View sceneRoot, View view, @GravityFlag int slideEdge, @Px int slideDistance) {
    switch (slideEdge) {
      case Gravity.LEFT:
        return createTranslationXAnimator(view, slideDistance, 0);
      case Gravity.TOP:
        return createTranslationYAnimator(view, -slideDistance, 0);
      case Gravity.RIGHT:
        return createTranslationXAnimator(view, -slideDistance, 0);
      case Gravity.BOTTOM:
        return createTranslationYAnimator(view, slideDistance, 0);
      case Gravity.START:
        return createTranslationXAnimator(
            view, isRtl(sceneRoot) ? slideDistance : -slideDistance, 0);
      case Gravity.END:
        return createTranslationXAnimator(
            view, isRtl(sceneRoot) ? -slideDistance : slideDistance, 0);
      default:
        throw new IllegalArgumentException("Invalid slide direction: " + slideEdge);
    }
  }

2. Transition

A Transition holds information about animations that will be run on its targets during a scene change

public abstract class Transition implements Cloneable {
public Transition addTarget(@NonNull View target) 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章