看这里学习的https://www.jianshu.com/p/54a6e2568cdd#comment-28749841
class ConstraintSet
class MotionLayout
ConstraintSet
这东西就是一堆view的约束条件的集合
实例化的方法
//Manually
c = new ConstraintSet(); c.connect(....);
//from a R.layout.* object
c.clone(context, R.layout.layout1);
//看下这方法可以看到上边的layout1布局,根元素需要是ConstraintLayout
public void clone(Context context, int constraintLayoutId) {
this.clone((ConstraintLayout)LayoutInflater.from(context).inflate(constraintLayoutId, (ViewGroup)null));
}
//from a ConstraintLayout
c.clone(clayout);
//还有这种弄一个布局的,当然了这个布局不要求根元素是ConstraintLayout
constraintSet.load(this, R.layout.keyframe_two);
顺道简单看下这个几个方法的源码,可以看到这里的布局或者ConstraintLayout里的所有child都必须设置id
理解
ConstraintSet 怎么说了,这东西可以理解成我们以前比如线性布局,相对布局的LayoutParams属性的集合。
嗯嗯,我觉得是差不多。
至于上边的clone,load其实都是从一个布局或者说从一个ConstraintLayout 里把所有的约束条件都提取出来而已。而且上边也说了里边的child必须要有个id,为啥了,是因为它要根据id和我们当前的布局对应起来。
最后这个set的使用方法都是这样的
constraintSet.applyTo(constraintLayout1)
也就是说它把自己从其他地方提取的【clone,load方法等】约束条件,应用到一个老的constraintLayout1上边。
constraintSet只能从布局提取吗?
答案是否定的,看下这个类里边的方法,好多个。
和普通的LayoutParams一样,它也可以手动设置一些属性的
比如有个老的按钮,我们现在给它设置新的属性如下
需要注意,新的属性,必须要保证这个view的具体位置,大小。也就是新的属性得保证这个view在某个位置。下边的,如果你不设置宽高,你会发现applyTo以后,按钮就不见了 。
constraintSet.setMargin(R.id.btn_test,ConstraintSet.LEFT,100)
constraintSet.setMargin(R.id.btn_test,ConstraintSet.TOP,200)
constraintSet.constrainHeight(R.id.btn_test,200)
constraintSet.constrainWidth(R.id.btn_test,100)
比如 public void setGoneMargin(int viewId, int anchor, int value)
我们在xml也可以设置这种属性 app:layout_goneMarginLeft="100dp"
这个anchor的值有6种,LEFT,RIGHT,TOP,BOTTOM,START ,END
方法是干啥的它就具体设置啥的,比如下边的设置goneMargin的
这个就是设置正常margin的
比如下边这些2个参数的,很多都是一个id,一个值,看名字也就大概知道干啥了,对应的都是xml的属性
刚开始的疑问
刚开始看到下边的代码,我还以为是替换布局了,还专门给老的布局的按钮弄个点击事件看有没有改变,后来发现自己想多了,看完上边分析,已经知道了,这里只是从这个布局或者说是constraintLayout里把每个child的约束属性读出来存起来而已,并不是替换这个布局。
之后applyTo就是把set里的约束,根据id找到对应的,然后把约束应用到老的布局上而已。
MotionLayout
public class MotionLayout extends ConstraintLayout implements NestedScrollingParent3
子类而已,所以父类有的它都有,很明显实现了嵌套滚动
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
2.0以后的库才有这个东西,这玩意需要一个motionscene的文件来实现动画,最低要求版本是14,也就是这个库的minSdkVersion =14
大家先看下文章开头的帖子,看完基本入门了,完事再去看上边官方文档,看下各个属性的描述,也就理解的八九不离十了,之后就是自己在demo里测试了
使用过程:按照以前的ConstraintLayout写完,然后换成MotionLayout,完事就会报错,提示你少个layoutDescription参数,按照提示就会自动生成一个xml文件了.
下边贴一个motionscene文件,这个文件是放在res的xml文件夹下边的,使用的时候是在布局文件里添加layoutDescription标签的.下图是一个最简单的.
总结一下,就是写2套ConstraintSet ,一套是起始位置,一套是结束位置,系统自动给我们添加一个过渡动画。当然,Transition里还可以添加一些关键帧之类的属性,后边讲
回头再看,下边有图先整体分析下结构,跳到代码后边
~下边代码比较老,有些attribute 换名字了,懒得改了~
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
android:id="@+id/my_transition"
app:constraintSetEnd="@+id/ending_set"
app:constraintSetStart="@+id/starting_set"
app:duration="4000">
<KeyFrameSet android:id="@+id/frameSet1">
<KeyPosition
app:curveFit="arc"
app:drawPath="path"
app:framePosition="30"
app:percentX="0.85"
app:target="@+id/btn_test"
app:type="deltaRelative" />
<KeyPosition
app:framePosition="60"
app:percentX="1"
app:target="@+id/btn_test"
app:type="deltaRelative" />
<KeyCycle
android:rotation="50"
app:framePosition="50"
app:target="@+id/btn_test"
app:wavePeriod="1"
app:waveShape="square" />
</KeyFrameSet>
<OnClick
app:mode="transitionToEnd"
app:target="@+id/btn_test" />
<OnSwipe app:dragDirection="dragDown"
app:touchAnchorId="@+id/btn_test"
app:touchAnchorSide="left"/>
</Transition>
<ConstraintSet android:id="@+id/starting_set">
<Constraint
android:id="@+id/btn_test"
android:layout_width="160dp"
android:layout_height="60dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/ending_set">
<Constraint //需要注意,这里要完整的宽高和约束属性额
android:id="@+id/btn_test"
android:layout_width="160dp"
android:layout_height="60dp"
android:layout_marginTop="100dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
</MotionScene>
MotionScene下有3种节点,StateSet试了下没反应,不管它了先
ConstraintSet
一般这玩意有两个对应上边的Transition标签
每个ConstraintSet里边就是N个Constraint标签了,用来处理想要进行动画的view的位置和大小的。这里的id和我们布局里的id一样的,你想哪个动,就在这里处理他们的位置。
Constraint
Constraint里就是对控件的约束条件,位置大小之类的,
如果要动态改变某个属性,可以用CustomAttribute标签,里边写上名字和值即可
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="BackgroundColor"
motion:customColorValue="#D81B60" />
</Constraint>
</ConstraintSet>
比如下边这个,如果Constraint里没写alpha,那么默认是不透明的,只有手指开始滑动,下边的CustomAttribute属性才能生效,
<CustomAttribute
motion:attributeName="Alpha"
motion:customFloatValue="0.3"/>
value有下边几种
attributeName The name of the attribute. Case sensitive. ( MyAttr will look for method setMyAttr(...)
customColorValue The value is a color looking setMyAttr(int )
customIntegerValue The value is an integer looking setMyAttr(int )
customFloatValue The value is a float looking setMyAttr(float )
customStringValue The value is a String looking setMyAttr(String )
customDimension The value is a dimension looking setMyAttr(float )
customBoolean The value is true or false looking setMyAttr(boolean )
Constraint支持的属性
android:id | Id of the View |
---|---|
[ConstraintLayout attributes] | Any attribute that is part of ContraintLayout layout is allowed |
[Standard View attributes] | A collection of view attributes supported by the system (see below) |
transitionEasing | define an easing curve to be used when animating from this point (e.g. curve(1.0,0,0,1.0)) or key words {standard | accelerate | decelerate | linear } |
pathMotionArc | the path will move in arc (quarter eclipses) or key words {startVertical | startHorizontal | none } |
transitionPathRotate | (float) rotate object relative to path taken |
drawPath | draw the path the layout will animate animate |
progress | call method setProgress(float) on this view (used to talk to nested ConstraintLayouts etc.) |
<CustomAttribute> | call a set"name" method via reflection |
<Layout> | Attributes for the ConstraintLayout e.g. layout_constraintTop_toTopOf |
<PropertySet> | currently only visibility, alpha, motionProgress,layout_constraintTag. |
<Transform> | All the view transform API such as android:rotation. |
<Motion> | Motion Layout control commands such as transitionEasing and pathMotionArc |
上述就是约束的基础属性了,写在这里的有个问题,它只能设置一个初始值,一个结束值,然后中间由系统自动处理。
比如开头demo里的viewpager,有5个页面,进度从0到100,如果我们只打算在最后一个页面出来的时候处理些东西,那么进度应该是在80到100之间的,这个上边的做不到了,就需要Transition里KeyFrameSet来处理了,比如,下边的透明度在进度从0到20的时候就变化完成了,进度20之后的就完全是不透明了
<KeyFrameSet>
<KeyAttribute
android:alpha="0"
app:framePosition="0"
app:motionTarget="@+id/btn_pre" />
<KeyAttribute
android:alpha="1"
app:framePosition="20"
app:motionTarget="@id/btn_pre" />
Transition
下边2个属性,一个是动画起始的时候的约束set,一个是结束的时候的约束set,指向上边介绍的ConstraintSet
查看demo,发现constraintSetStart 或者constraintSetEnd 也可以指向一个布局文件。
constraintSetStart ConstraintSet to be used as the start constraints or a layout file to get the constraint from
constraintSetEnd ConstraintSet to be used as the end constraints or a layout file to get the constraint from
OnSwipe (optional)
处理触摸事件的
Attributes | Description |
---|---|
touchAnchorId | Have the drag act as if it is moving the "touchAnchorSide" of this object |
touchRegionId | Limits the region that the touch can be start in to the bounds of this view (even if the view is invisible) |
touchAnchorSide | The side of the object to move with {top,left,right,bottom} |
maxVelocity | limit the maximum velocity (in progress/sec) of the animation will on touch up. Default 4 |
dragDirection | which side to swipe from {dragUp,dragDown,dragLeft,dragRight} |
maxAcceleration | how quickly the animation will accelerate (progress/sec/sec) and decelerate on touch up. Default 1.2 |
dragScale | scale factor to adjust the swipe by. (e.g. 0.5 would require you to move 2x as much) |
moveWhenScrollAtTop | If the swipe is scrolling and View (such as RecyclerView or NestedScrollView) do scroll and transition happen at the same time |
autoComplete | swipe automatically animates to start or end. Default is true. Warning: turning this off and using time cycles can result in continuous animations. |
dragDirection 就是手指滑动的方向了,左右,上下 触发动画事件,拖动到一半松手,动画自动就还原了。
touchAnchorId 这个东西好像是那个在进行动画的view的id。
OnClick (optional)
targetId | What button triggers Transition. |
clickAction | Direction for buttons to move the animation. mode: transitionToEnd, toggle, transitionToStart, jumpToEnd, jumpToStart |
这个就2个属性,target 指向一个view的id,指定点击哪个view触发事件
第二个就是mode了,有5种 transitionToEnd, toggle, transitionToStart, jumpToEnd, jumpToStart
就是前边Transition里的constraintSetStart和constraintSetEnd 决定从start到end还是end到start
jumpToEnd, jumpToStart好像就是直接结束动画了,以前测试版会异常,正式版试了下好像不挂,就是直接从start切换为end状态或者反过来.
KeyFrameSet关键帧的集合
前边一堆,我们也看到我们的动画只有开头和结束两种状态,中间其实就是线性的对x和y的位置进行过渡的。
比如view开始在左上角,结束在右下角,那么正常动画就是斜线了。
如果我们需要view拐个弯啥的,咋办了。KeyFrameSet就是干这个用的,可以插入一些中间帧。
研究下简单的两种
KeyPosition
这个是修改关键帧的位置的
attribute | descrption |
---|---|
motionTarget | Id of the View or a regular expression to match layout_ConstraintTag |
framePosition | The point along the interpolation 0 = start 100 = end |
transitionEasing | define an easing curve to be used when animating from this point (e.g. curve(1.0,0,0,1.0)) or key words {standard | accelerate | decelerate | linear } |
pathMotionArc | The path will move in arc (quarter eclipses) key words {startVertical | startHorizontal | flip | none } |
keyPositionType | how this keyframe's deviation for linear path is calculated {deltaRelative | pathRelative|parentRelative} |
percentX | (float) percent distance from start to end along X axis (deltaRelative) or along the path in pathRelative |
percentY | (float) Percent distance from start to end along Y axis (deltaRelative) or perpendicular to path in pathRelative |
percentWidth | (float) Percent of change in the width. Note if the width does not change this has no effect.This overrides sizePercent. |
percentHeight | (float) Percent of change in the width. Note if the width does not change this has no effect.This overrides sizePercent. |
curveFit | path is traced |
drawPath | Draw the path of the objects layout takes useful for debugging |
sizePercent | If the view changes size this controls how growth of the size. (for fixed size objects use KeyAttributes scaleX/X) |
curveFit | selects a path based on straight lines or a path based on a monotonic spline {linear|spline} |
motionTarget :指定是哪个view,因为动画过程可能有N个在发生变化,总得说下这个是给谁的
framePosition 这个就是动画运行的百分比,0到100
keyPositionType:deltaRelative | pathRelative|parentRelative 下边说
percentX 指定在framePostion这个时间点的时候x应该在啥位置,这个是个百分比,0到1之间的值,具体是谁的百分比要看keyPositionType,delta话这里就是target的start和end的x坐标的差值;path的话就是值起点到终点的路径的百分比。parent顾名思义,指的就是MotionLayout这个容器的横轴的百分比
percentY一个道理
sizePercent 这个和上边x,y其实一样,不过这里是对大小改变的view来说的。
https://medium.com/google-developers/defining-motion-paths-in-motionlayout-6095b874d37
上边的帖子有讲坐标轴,percentX,percentY是有正负一说的,而且所对应的值也是path这个坐标系统对应的值,帖子里的parentRelative的图片感觉y轴画反了,大家自己实验下吧。
-
pathRelative
正负咋区分?
简单,从start 到end就是x轴的正向,y轴就是start为圆点,顺时针旋转90度,ok.
-
deltaRelative
也是从起点到终点,不过坐标轴是和屏幕一样,是横竖的,不像parentRelative坐标轴可能是歪的,
至于正负,从起点往终点的方向就是正的,反之就是负的
-
parentRelative
这个就更简单了,坐标轴是parent,左上角是圆点,和我们平时的坐标系统一样,下图好像y轴反了?
KeyAttribute
attribute | descrption |
---|---|
motionTarget | Id of the View or a regular expression to match layout_ConstraintTag |
framePosition | The point along the interpolation 0 = start 100 = end |
curveFit | selects a path based on straight lines or a path based on a monotonic spline {linear|spline} |
transitionEasing | Define an easing curve to be used when animating from this point (e.g. curve(1.0,0,0,1.0)) or key words {standard , accelerate , decelerate , linear } |
transitionPathRotate | (float) rotate object relative to path taken |
drawPath | draw the path the layout will animate animate |
motionProgress | call method setProgress(float) on this view (used to talk to nested ConstraintLayouts etc.) |
[standard view attributes] | A collection of post layout view attributes see below |
<CustomAttribute> | call a set"name" method via reflection |
CustomAttribute
attribute | descrption |
---|---|
attributeName | The name of the attribute. Case sensitive. ( MyAttr will look for method setMyAttr(...) |
customColorValue | The value is a color looking setMyAttr(int ) |
customIntegerValue | The value is an integer looking setMyAttr(int ) |
customFloatValue | The value is a float looking setMyAttr(float ) |
customStringValue | The value is a String looking setMyAttr(String ) |
customDimension | The value is a dimension looking setMyAttr(float ) |
customBoolean | The value is true or false looking setMyAttr(boolean ) |
attributeName这个需要注意一下,这里的名字不是xml里的,而是控件对应的setXXX方法对应的XXX。
举例,
设置背景,在xml里是android:background="#222",想测试下动态修改背景图片,愣是没找到咋弄,CustomAttribute 里好像没有设置背景图片的.
<CustomAttribute app:attributeName="backgroundColor" app:customColorValue="@color/colorPrimaryDark"/>
<CustomAttribute app:attributeName="backgroundColor" app:customColorValue="#2196F3"/>
这个是修改在关键帧的地方控件的属性
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
motion:framePosition="50"
motion:target="@id/button" />
KeyCycle
app:wavePeriod 这个就是波形的次数,比如正弦从0到360是一次,你要执行2次,这里就写个2
app:framePosition 和上边一样,指定一个时间点,在这个附近应用Cycle
android:rotation="50" 下边列子是一个角度旋转的动画,这里指定50,就是最大值了。
android:translationY="50dp" 表示y轴的最大偏移量就是50dp
实际的动画受到波形的影响。比如波形是正弦,那么正弦的值从0到1到0再到-1最后又到0
那么实际的角度 就是不停的变化了,就是那个50度乘以这个正弦值。
其他的波形逻辑是一样的,不过那些波形的值的变化没研究,不太清楚。
举例如下
<KeyCycle
app:target="@+id/btn_test"
android:rotation="50"
app:framePosition="50"
app:wavePeriod="1"
app:waveShape="sin" />
下边是支持的标准属性,用这些属性,加上波形,就可以让控件忽明忽暗,一会变大一会缩小,旋转了。看自己需求了。。。
KeyTrigger
attribute | description |
---|---|
motionTarget | Id of the View or a regular expression to match layout_ConstraintTag |
framePosition | The point along the interpolation 0 = start 100 = end |
onCross | (method name) on crossing this position call this methods on the target |
onPositiveCross | (method name) on forward crossing of the framePosition call this methods on the target |
onNegativeCross/td> | (method name) backward crossing of the framePosition call this methods on the target |
triggerSlack | (float) do not call trigger again if the framePosition has not moved this fraction away from the trigger point |
triggerId | (id) call the TransitionListener with this trigger id |
motion_postLayoutCollision | Define motion pre or post layout. Post layout is more expensive but captures KeyAttributes or KeyCycle motions. |
motion_triggerOnCollision | (id) Trigger if the motionTarget collides with the other motionTarget |
举个列子,在进度为50的时候会执行onCross里的方法performClick,谁的点击事件?自然是mationTarget指定的,
<KeyTrigger
app:framePosition="50"
app:motionTarget="@+id/view_pin"
app:onCross="performClick"
app:onNegativeCross="performLongClick"
app:triggerSlack="1" />
可以理解为触发器,就是在某一位置framePostion的时候会回调容器MotionLayout的TransitionListener里的onTransitionTrigger 的方法,参数triggerId就是上边KeyTrigger里定义的,没有定义的话就是-1,progress就是当前的进度从0到1,positive咋说了,比如我们上边写的触发机制是position 50,如果比这个晚的比如返回的progress是0.51那么就是true,如果是0.49这样的就是false
public void addTransitionListener(MotionLayout.TransitionListener listener)
public abstract void onTransitionTrigger (MotionLayout motionLayout,
int triggerId,
boolean positive,
float progress)
试了下,只有第一次start到end,和从end返回start 会触发trigger回调,其他时候就没有了,onCross方法也只有第一次有,后边就没了.不太清楚是姿势不对还是本来就这样,待研究.
补充
demo里看到下边的代码,初始不知道干啥的
<com.google.androidstudio.motionlayoutexample.helpers.ExampleFlyinBounceHelper
android:id="@+id/helper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="imageView9"/>
然后看了下它的类,发现里边就是对某个view进行了一个平移动画
public class ExampleFlyinBounceHelper extends ConstraintHelper {
protected ConstraintLayout mContainer;
//构造方法省略。
@Override
public void updatePreLayout(ConstraintLayout container) {
if (mContainer!=container) {
View[] views = getViews(container);
for (int i = 0; i < mCount; i++) {
View view = views[i];
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", - 2000, 0).setDuration(1000);
animator.setInterpolator(new BounceInterpolator());
animator.start();
}
}
mContainer = container;
}
}
然后简单研究下这个类
可以看到它主要是读取了一个属性constraint_referenced_ids
我们在ConstraintLayout里见过这个,就是关联id,多个的话用逗号隔开。
如下
app:constraint_referenced_ids="imageButton2,imageView9"
源码简化部分,以及可能用到的方法,变量等
public abstract class ConstraintHelper extends View {
protected int[] mIds = new int[32];//保存关联的id,当然了这个数组比较大
protected int mCount;//关联的id个数
public int[] getReferencedIds() {//这个是真实的id个数的数组
return Arrays.copyOf(this.mIds, this.mCount);
}
TypedArray a = this.getContext().obtainStyledAttributes(attrs, styleable.ConstraintLayout_Layout);
if (attr == styleable.ConstraintLayout_Layout_constraint_referenced_ids) {
this.mReferenceIds = a.getString(attr);
this.setIds(this.mReferenceIds);
}
//关联的这些id对应的view数组
protected View[] getViews(ConstraintLayout layout){
}
//然后下边几个方法会不停的调用
public void updatePreLayout(ConstraintLayout container){
//这个应该是预加载,在measure和layout之前。
}
//首次加载view,我们知道measure和layout会执行2次,这个也是
public void updatePostLayout(ConstraintLayout container) {
}
public void updatePostMeasure(ConstraintLayout container) {
}
- 布局初始状态加载完成,当我们移动布局,
会多次执行
updatePreLayout
updatePostMeasure
当移动结束的时候会执行
updatePostLayout - 调用motionLayout.transitionToStart() 自动移动的话,
执行顺序如下
updatePostMeasure
updatePreLayout
updatePostMeasure
updatePostLayout
就如demo里写的,这个helper类,主要就是用来对一些view做特殊处理。它本身默认宽高都是0的。
MotionLayout的常用方法
设置当前的进度。
public void setProgress(float pos)
移动到开始或结束位置
public void transitionToStart() {
this.animateTo(0.0F);
}
public void transitionToEnd() {
this.animateTo(1.0F);
}
继续学习
Defining motion paths in MotionLayout
KeyPosition
framePosition:这个就是关键帧的位置?咋说了,总的帧是100,我们知道有个interpolator的东西,以线性的interpolator来举例。从起点A【50,100】移动到终点B【150,100】,需要100秒,在中心点【100,100】的地方,帧是50,耗时一半也就是50秒, 现在我们弄个关键帧,framePosition=50,完事修改这个帧的位置为C【60,100】那么结果就是从起点A【50,100】移动到C【60,100】耗时50秒,后边C到终点B【150,100】耗时50秒。
下边有效果图,看那虚线密度不一样也能大概看出来。
主要学习下
percentX,percentY【大小从0到1f】,
keyPositionType[3种:parentRelative,pathRelative,deltaRelative] 之间的关系
<KeyPosition
app:keyPositionType="pathRelative"
app:percentY="-0.25"
app:percentX="0.5"
app:framePosition="50"
app:transitionEasing="accelerate"
app:motionTarget="@id/btn_test"/>
下边几种起始点和结束点都一样,起始点在中心,结束点在左上角有个margin而已。分别是A和B
C就是我们的keyPostiion
-
parentRelative
实际结果来看,坐标原点还是左上角,x轴往右是正的,y轴往下是正的。所以下图感觉画的不太合适
percentX 就是parent水平方向的百分比
percentY:parent的垂直方向的百分比
代码
<KeyPosition
app:keyPositionType="parentRelative"
app:percentX="0.75"
app:percentY="0.25"
app:framePosition="50"
app:transitionEasing="accelerate"
app:motionTarget="@id/btn_test"/>
效果图如下
-
deltaRelative
percentX,percentY 对应的百分比,是参照起点和终点之间的x,y轴距离的,正负也是按照从起点到重点来的,
如果B在A的右边,那么percentX为正就是右边的
参考这个
代码
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="0.75"
app:percentY="0.25"
app:framePosition="50"
app:transitionEasing="accelerate"
app:motionTarget="@id/btn_test"/>
- pathRelative
就是说x轴就是起点start连接到终点end这段距离,至于y轴就是和这条路径垂直的,至于方向x轴顺时针90度那个是y轴的方向
代码如下
<KeyPosition
app:keyPositionType="pathRelative"
app:percentX="0.5"
app:percentY="0.5"
app:framePosition="50"
app:transitionEasing="accelerate"
app:motionTarget="@id/btn_test"/>
pathMotionArc
大家知道,没有中间点的话,start到end之间就是线性移动的,如果想要弧形移动咋办,非常简单
You will simply need to add the motion:pathMotionArc attribute to the starting Constraint, to switch from the default linear motion to an arc motion
<ConstraintSet
android:id="@+id/start" >
<Constraint
app:pathMotionArc="startVertical"
android:id="@id/btn_test"
这个属性有4种值:设置的话一般就startVertical或startHorizontal,要不就不写
none:默认的,就是线条
startVertical:圆弧起点往下或者往上,根据终点绝点
startHorizontal: 圆弧从起点往左或者往右,根据终点决定,
flip:这个结果和startHorizontal一样,其实这个需要参照物的,可没有
看下上边链接里写的flipping the current arc direction ,反转当前圆弧的方向。
上边的是没有中间点的情况,如果有中间点咋办?
中间点可以控制自己之后的圆弧方向,或者还原成直线
如下代码,一样的属性,value也是4种可选,
不过这里的flip就有实际意义了,如果之前的是startVertical,那么这里flip就相当于startHorizontal,同理之前是startHorizontal,这里就相当于startVertical
如果是none,那么这后边的就成直线了。
<KeyFrameSet>
<KeyPosition
app:pathMotionArc="flip"
app:framePosition="50"
transitionEasing
这个影响插值器的,默认的是线性的,这里可以修改,系统已经写好了几个可以用的
<Constraint
app:pathMotionArc="startHorizontal"
app:transitionEasing="decelerate"
可用的standard, accelerate, decelerate
还有这种cubic(float, float , float, float) 就是个一阶贝塞尔曲线,所以这里是2个点的坐标,百分比,从0到1的
当然可以大于1,不过大于1你会发现,时间本来是2秒,结果1秒可能就到终点了。
比如
app:transitionEasing="cubic(0.0, 0.0, 0.2, 1)"
KeyAttribute
也是处理中间状态的,不过这里是处理一些基本属性的,比如缩放,旋转,平移,透明度等。
<KeyFrameSet>
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
motion:framePosition="50"
motion:target="@id/button" />
</KeyFrameSet>
Supported Attributes
The attributes you can use out of the box are view attributes:
android:visibility, android:alpha, android:elevation, android:rotation, android:rotationX, android:rotationY, android:scaleX, android:scaleY, android:translationX, android:translationY, android:translationZ
这些属性和sdk版本挂钩的,如下2个,21才支持,所以21以下我们设置是无用的
android:elevation was introduced in SDK 21
android:translationZ was introduced in SDK 21
Custom Attributes
修改控件属性的
可以放在Constraint里,也可以放在KeyAttribute里
看几个例子
<Constraint
android:id="@id/btn_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginTop="80dp"
android:layout_marginRight="30dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<CustomAttribute app:attributeName="background"
app:customColorDrawableValue="@color/book_content_bg_color"/>
</Constraint>
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
app:framePosition="50"
app:motionTarget="@id/btn_test">
<CustomAttribute 省略 />
</KeyAttribute>
attributeName:这个是控件本身xml里支持的,名字就是xml里的,首字母大小写都可以,名字不对不会生效,自己试下就知道了。
剩下的是个value,有好几种,就是区分是颜色,文字,小数整数等的,看名字也就大概知道了
这里说下背景色的
<CustomAttribute
app:attributeName="background"
app:customColorDrawableValue="@color/colorAccent" />
<CustomAttribute
app:attributeName="BackgroundColor"
app:customColorValue="@color/color_blue" />
比如修改文字
<CustomAttribute
app:attributeName="text"
app:customStringValue="开始"/>
touchAnchorId
需要说一下,这个id是必须的,要不手指触摸屏幕滑动的时候直接就挂了,说找不到view之类的错误
<OnSwipe app:dragDirection="dragDown"
app:touchAnchorId="@+id/btn_test"
app:touchAnchorSide="left"/>
使用经验
- onSwipe
这个标签作用就是手指滑动就可以执行动画了,啥参数也没有,默认是手指上下滑动进行移动。
<onSwipe />
touchAnchorId:这个可以不写,它默认应该有一个吧。就是ConstraintSet里的某个Constraint。 如果写了,必须写Constraint对应的id,比如你有2个Constraint,写任何一个的id都可以。写谁就参照谁来移动。 如果你写了一个非Constraint的id,比如MotionLayout里的某个不变化的控件id,那么移动的会后你会发现没有过程,start到end之间是瞬间切换的。
- app:pathMotionArc="startVertical"
前边介绍过这个,两个点直接本来是直线,用这个可以变成弧线。startHorizontal一个道理
不过实际使用中发现个问题,我的view变化是x轴的【y轴变化也一样,换句话说,不能是横线或竖线变化】,我也想看看x轴的,它咋弄,结果是它挂了。哈哈。
反正start和end的x,或者y坐标不能一样,至少有点偏差才能用上边的属性
java.lang.ArrayIndexOutOfBoundsException: length=1; index=1
at androidx.constraintlayout.motion.utils.ArcCurveFit.getPos(ArcCurveFit.java:51)