手动实现布局过渡效果(Layout transition)- 第二部分

英文原文: Manual Layout Transitions – Part 2 

这一部分是最简单的部分 - 译者注。

前面我们已经看到了如何实现Dirty Phrasebook中的动画,这篇文章中我们将更进一步讨论如何自动产生两个布局状态之间的过渡动画。

安卓的Transition框架引入了场景的概念,它代表了特定的布局状态。我们用最简单的方式来定义场景,只定义两个不同的布局,一个代表默认的布局,一个代表进入输入模式时候的布局。我们先创建这两个布局 - 基于Dirty Phrasebook中的布局,但是为了更好理解,简化了一些东西。

首先是默认的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:clipChildren="false">
  <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">
    <View
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:background="?attr/colorPrimary" />
    <View
      android:id="@+id/focus_holder"
      android:layout_width="0dp"
      android:layout_height="0dp"
      android:focusableInTouchMode="true">
      <requestFocus />
    </View>
    <android.support.v7.widget.CardView
      android:id="@+id/input_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_alignParentBottom="true"
      android:layout_below="@id/toolbar">
      <EditText
        android:id="@+id/input"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:inputType="textMultiLine" />
      <ImageView
        android:id="@+id/input_done"
        android:layout_width="32dip"
        android:layout_height="32dip"
        android:layout_alignBottom="@id/input"
        android:layout_alignEnd="@id/input"
        android:layout_alignRight="@id/input"
        android:layout_gravity="bottom|end"
        android:layout_margin="8dp"
        android:background="@drawable/done_background"
        android:contentDescription="@string/done"
        android:padding="2dp"
        android:src="@drawable/ic_arrow_forward"
        android:visibility="invisible" />
    </android.support.v7.widget.CardView>
  </RelativeLayout>
  <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">
    <android.support.v7.widget.CardView
      android:id="@+id/translation"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="8dp">
      <View
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="?attr/colorPrimary" />
    </android.support.v7.widget.CardView>
  </FrameLayout>
</LinearLayout>

这和前一篇文章用到的基本布局是一样的,但是也做了一点点简化(比如Toolbar直接用一个带背景色的view替代)。我们所关心的控件分别是ID为这些的控件:toolbar, focus_holder, input,input_view, input_done, 以及 translation。

布局效果是这样的:

blob.png

而当处于输入模式的时候,布局时这样的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:clipChildren="false">
  <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">
    <View
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:background="?attr/colorPrimary" />
    <View
      android:id="@+id/focus_holder"
      android:layout_width="0dp"
      android:layout_height="0dp"
      android:focusableInTouchMode="true" />
    <android.support.v7.widget.CardView
      android:id="@+id/input_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_alignParentBottom="true"
      android:layout_alignParentTop="true"
      android:layout_marginBottom="?attr/actionBarSize">
      <EditText
        android:id="@+id/input"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:inputType="textMultiLine">
        <requestFocus />
      </EditText>
      <ImageView
        android:id="@+id/input_done"
        android:layout_width="32dip"
        android:layout_height="32dip"
        android:layout_alignBottom="@id/input"
        android:layout_alignEnd="@id/input"
        android:layout_alignRight="@id/input"
        android:layout_gravity="bottom|end"
        android:layout_margin="8dp"
        android:background="@drawable/done_background"
        android:contentDescription="@string/done"
        android:padding="2dp"
        android:src="@drawable/ic_arrow_forward"
        android:visibility="visible" />
    </android.support.v7.widget.CardView>
  </RelativeLayout>
  <FrameLayout
        android:layout_width="match_parent"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">
    <android.support.v7.widget.CardView
      android:id="@+id/translation"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="8dp"
      android:visibility="invisible">
      <View
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="?attr/colorPrimary" />
    </android.support.v7.widget.CardView>
  </FrameLayout>
</LinearLayout>

几乎和前面的布局一样,区别是:

  1. input_view  置于父布局的顶部,而不是在toolbar的下面,准确的说是挡住了toolbar。   

  2. input_done 现在是可见的了 - 之前是不可见的。

  3. translation 现在是不可见的了 - 之前是可见的。 

效果如下:

blob.png

这两个布局代表了两个不同的UI状态 - 如果使用的是 Transitions API,也就分别对应两个场景。

这次我们要做的事情就是当进入和退出输入模式的时候,在这两个布局之间切换。跟之前一样,我们在MainActivity中检测焦点的改变:

public class MainActivity extends AppCompatActivity {
    private View input;
    private TransitionController focusChangeListener;
    private View.OnClickListener onClickListener;
    private View focusHolder;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        focusChangeListener = Part2TransitionController.newInstance(this);
        onClickListener = new View.OnClickListener() {
            @Override
            public void onClick(@NonNull View v) {
                focusHolder.requestFocus();
            }
        };
        setContentView(R.layout.activity_part2);
    }
    @Override
    public void setContentView(int layoutResID) {
        if (input != null) {
            input.setOnFocusChangeListener(null);
        }
        super.setContentView(layoutResID);
        input = findViewById(R.id.input);
        View inputDone = findViewById(R.id.input_done);
        focusHolder = findViewById(R.id.focus_holder);
        input.setOnFocusChangeListener(focusChangeListener);
        inputDone.setOnClickListener(onClickListener);
    }
}

当我们检测到焦点改变的时候,我们同时也按照一定的逻辑添加setContentView()方法。我们将通过调用setContentView() 来切换布局。

同时需要注意到,调用setContentView()的时候view树的结构发生了改变,因此每次布局改变的时候都需要“find the View”。当然也需要去掉 focus listener ,然后重新把它和新的input view关联。

这些都是在被重写的setContentView() 中所做的。 --译者注。

跟之前一样,我们有一个TransitionController来处理焦点的改变:

public class Part2TransitionController extends TransitionController {
    Part2TransitionController(WeakReference<Activity> activityWeakReference, AnimatorBuilder animatorBuilder) {
        super(activityWeakReference, animatorBuilder);
    }
    public static TransitionController newInstance(Activity activity) {
        WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
        AnimatorBuilder animatorBuilder = AnimatorBuilder.newInstance(activity);
        return new Part2TransitionController(activityWeakReference, animatorBuilder);
    }
    @Override
    protected void enterInputMode(Activity activity) {
        activity.setContentView(R.layout.activity_part2_input);
    }
    @Override
    protected void exitInputMode(Activity activity) {
        activity.setContentView(R.layout.activity_part2);
    }
}

目前所作的事情就是只是调用setContentView()来切换布局。

如果你现在尝试一下,你会发现虽然我们成功在两个状态间切换,但是缺乏动画效果。在下一篇文章中,我们将看到如何让它们动起来。

这篇文章的源代码在 这里