ConstraintLayout: Circular Positioning
原文:https://medium.com/devnibbles/constraintlayout-circular-positioning-9489b11cb0e5
ConstraintLayout是一个不错的库,目前已经到了1.1版本,昨天刚发布了新的beta版本(beta3)。
这个版本中比较有趣的是一个叫做Circular Positioning的东西。顾名思义,它可以约束一个view相对于另一个view的弧度和半径。
view是以各自的中心为参照来约束的,跟通常以 start/end/top/bottom 或者 baseline 来约束是不同的
有了这种约束view的方式,一些平常很难实现的布局和动画实现起来就非常简单了。比如模拟行星的运动,太阳在中心,行星绕着它做旋转。
如果没有ConstraintLayout的这个新功能的话,你可能会用自定义view和在canvas上绘制bitmaps来实现。这样做是可行的,但可能需要做很多工作,而且要让行星与让自定义view外面的其它view交互非常困难。
让我们创建一个示例项目来演示一些利用这个新的约束的技巧。在这个示例中我们将让三个行星绕着太阳转。最终的结果如下:
// Java code - Old School :-)
public class CircleConstraintsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_orbits);
ImageView earthImage = findViewById(R.id.earth_image);
ImageView marsImage = findViewById(R.id.mars_image);
ImageView saturnImage = findViewById(R.id.saturn_image);
ValueAnimator earthAnimator = animatePlanet(earthImage, TimeUnit.SECONDS.toMillis(2));
ValueAnimator marsAnimator = animatePlanet(marsImage, TimeUnit.SECONDS.toMillis(6));
ValueAnimator saturnAnimator = animatePlanet(saturnImage, TimeUnit.SECONDS.toMillis(12));
earthAnimator.start();
marsAnimator.start();
saturnAnimator.start();
}
private ValueAnimator animatePlanet(ImageView planet, long orbitDuration) {
ValueAnimator anim = ValueAnimator.ofInt(0, 359);
anim.addUpdateListener(valueAnimator -> {
int val = (Integer) valueAnimator.getAnimatedValue();
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) planet.getLayoutParams();
layoutParams.circleAngle = val;
planet.setLayoutParams(layoutParams);
});
anim.setDuration(orbitDuration);
anim.setInterpolator(new LinearInterpolator());
anim.setRepeatMode(ValueAnimator.RESTART);
anim.setRepeatCount(ValueAnimator.INFINITE);
return anim;
}
}
页面布局非常简单:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#ffffff"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/sun_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/sun"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/earth_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/earth"
app:layout_constraintCircle="@+id/sun_image"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="90dp" />
<ImageView
android:id="@+id/mars_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/mars"
app:layout_constraintCircle="@+id/sun_image"
app:layout_constraintCircleAngle="110"
app:layout_constraintCircleRadius="130dp" />
<ImageView
android:id="@+id/saturn_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/saturn"
app:layout_constraintCircle="@+id/sun_image"
app:layout_constraintCircleAngle="235"
app:layout_constraintCircleRadius="180dp" />
</android.support.constraint.ConstraintLayout>
此时,我觉得你应该同意我们的app有了一个不错的外观,可以放到 Google Play Store了,但是让我们进一步挖掘ConstraintLayout的潜力,在点击太阳的时候添加一个ConstraintSet的动画。这里我们将显示每个行星的详情。
强大的ContraintLayout 和 ConstraintSet 可以让我们不必担心在点击太阳的时候行星从动画过渡到详情的问题,行星将自然的从当前位置过渡到最终位置。
为此,我们首先在前面的布局文件中添加一些隐藏的TextView,并给每个TextView一个名字,然后在第二个布局文件中显示这些TextViews,并把它们的constraints设置为挨着各自的行星。我们还修改行星的constraints,让它垂直布局。最终的XML如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:background="#ffffff">
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="130dp" />
<ImageView
android:id="@+id/sun_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/sun"
app:layout_constraintEnd_toEndOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/sun_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/lorum_short"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="@id/sun_image" />
<ImageView
android:id="@+id/earth_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:src="@drawable/earth"
app:layout_constraintEnd_toEndOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sun_image" />
<TextView
android:id="@+id/earth_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/lorum_short"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="@id/earth_image" />
<ImageView
android:id="@+id/mars_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:src="@drawable/mars"
app:layout_constraintEnd_toEndOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/earth_image" />
<TextView
android:id="@+id/mars_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/lorum_short"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="@id/mars_image" />
<ImageView
android:id="@+id/saturn_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:src="@drawable/saturn"
app:layout_constraintEnd_toEndOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mars_image" />
<TextView
android:id="@+id/saturn_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/lorum_short"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="@id/saturn_image" />
</android.support.constraint.ConstraintLayout>
Activity的最终代码
private ConstraintSet orbitsConstraint = new ConstraintSet();
private ConstraintSet detailsConstraint = new ConstraintSet();
private ConstraintLayout mConstraintLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_orbits);
mConstraintLayout = findViewById(R.id.root);
orbitsConstraint.clone(mConstraintLayout);
detailsConstraint.clone(this, R.layout.activity_details);
ImageView sunImage = findViewById(R.id.sun_image);
ImageView earthImage = findViewById(R.id.earth_image);
ImageView marsImage = findViewById(R.id.mars_image);
ImageView saturnImage = findViewById(R.id.saturn_image);
ValueAnimator earthAnimator = animatePlanet(earthImage, TimeUnit.SECONDS.toMillis(2));
ValueAnimator marsAnimator = animatePlanet(marsImage, TimeUnit.SECONDS.toMillis(6));
ValueAnimator saturnAnimator = animatePlanet(saturnImage, TimeUnit.SECONDS.toMillis(12));
startAmin(earthAnimator, marsAnimator, saturnAnimator);
sunImage.setOnClickListener(view -> {
cancelAnim(earthAnimator, marsAnimator, saturnAnimator);
TransitionManager.beginDelayedTransition(mConstraintLayout);
detailsConstraint.applyTo(mConstraintLayout);
});
}
你可以看到我们设置了Sun图片的点击事件,同时我们还整理了一下代码,把 starting/cancelling动画的方法放在了帮助类中。
我很快就会把代码提交到Github,但是在这之前我希望先听到你们的反馈,请在下面留言。