布局切换动画在Material design中是一个重要的方面,因为它们能够指明应用的工作流程,并且能够将UI上的可视化元素绑定在一起作为用户的导航。两个重要的工具可以实现这种效果,分别为Activity转场动画和布局动画(Layout Transitions)。然后布局动画需要在API 19及其之后才支持。在这一系列文章中,我们会学习到即使在无法调用transitions APIs时如何实现很好的转场动画。
布局切换框架引入了代表特定布局状态的Scenes概念,也就是场景。我们会定义两个分离的布局来模仿这些,其中一个代表默认的视图,另一个代表我们进入输入模式的视图。我们先来创建两个布局,它们都是基于Dirty Phrasebook布局,当然我们会做一些小修改以便大家能够更容易理解。
首先是默认布局。
res/layout/activity_part2.xml
<?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>
这个布局与我们上一篇使用的布局基本一致,但是稍微有一点小修改。现在我们感兴趣只有id为toolbar, focus_holder, input, input_view, input_done和 translation的视图控件。
它看起来是这样的:
The layout for when we’re in input mode is:
下面是进入输入模式的布局 :
res/layout/activity_part2_input.xml
<?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>
该布局和上一布局也是基本相同,它们的区别在于 :
效果如下 :
两个布局代表UI的两个状态,如果我们使用Transitions API那么它们就是我们所谓的场景。
我们现在要做的就是在这两个布局之间进行状态切换,也就是进入、退出输入模式。首先我们需要检测MainActivity中的焦点 :
part2/mainActivity.java
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时就可以切换这两个布局,此时View的层级关系也会随着改变。因此我们每次都需要找到布局中的子视图,焦点listener我们也需要移除并且重新设置一个到input视图中。
和原来一样,我们需要一个TransitionController来处理焦点变化:
.part2.Part2TransitionController.java
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);
}
}
现在我们需要做的只是调用Activity的setContentView函数来切换两个布局。
如果我们现在运行上面的代码,我们可以看到两个布局毫无过度的切换,这必然不是我们想要的。在下一篇文章中,我们将讲解如何在布局切换时添加动画。
本文的源代码在这里。
Manual Layout Transitions – Part 1 by Styling Android is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Permissions beyond the scope of this license may be available at http://blog.stylingandroid.com/license-information.
Copyright© 2013-2019