我报名参加金石计划1期应战——分割10万奖池,这是我的第4篇文章,点击检查活动详情
我自己公司的业务需求上是根本不会涉及动画相关的,也可以说根本两年没怎么做过动画相关的开发,这次写这篇文章是由于读到一篇比较好的文章,想把它记载下来,今后有做动画作用的需求的话就能快速上手。 参阅《Android 开发艺术探究》 7.3.4 对恣意特点做动画
众所周知Android里边动画有3种,View动画、帧动画和特点动画。不同的场景使用不同的动画,对控件而言,你想完结一个自定义动画的作用,也便是随心,那就使用特点动画会比较好,由于View动画只供给了简略的平移、翻转、缩放、透明度。 当然这儿也不会去介绍一些基础的内容。为什么独自说这一末节,由于我觉得这个地方是这章(第七章)最经典的地方。
一. 对恣意特点做动画
依照书上的逻辑来讲。 一开始举了一个栗子,给Button加个动画,让这个Button的宽度增加500px。 直接写特点动画的代码:
ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();
这儿是对这个Button的 width特点做动画,也便是对宽度这个特点做动画,可是并不能完结咱们想要的宽度增加500px的作用。这时候就先引出了一个重要的结论: 特点动画要求动画作用的目标供给该特点的get和set办法 这个很重要,意思便是说,这个mButton目标,没有供给getWidth和setWidth办法。其实这说法有点那啥,由于Button是有getWidth和setWidth的办法,可是为什么没生效呢? 这是展示了setWidth的源码
public void setWidth(int pixels) {
mMaxWidth = mMinWidth = pixels;
mMaxWidthMode = mMinWidthMode = PIXELS;
requestLayout();
invalidate();
}
发现这儿并不是设置Button的宽度,而是设置Button的最大宽度和最小宽度。 也便是说在上面的动画中:真的的是随着时刻的改动去改动控件的最大最小宽度,所以视觉看不出作用,由于实践作用的特点不是你想要去让它作用的特点 所以想要完结作用,应该要供给该特点正确的get/set办法
书上又写出了这样的一个结论(原话): 针对上诉问题,官方文档上告诉咱们有3种解决办法:
- 给你的目标加上get和set办法,假如你有权限的话
- 用一个类来包装原始目标,直接为其供给get和set办法
- 选用ValueAnimator,监听动画过程,自己完结特点的变化
第一个计划必定不可行,系统的View不是咱们说改就改的,然后用两个Demo分别来描述办法2和办法3(不能仿制要手写,好不想写)
办法2:
private static class ViewWrapper{
private View mTarget;
public ViewWrapper(View target){
mTarget = target;
}
public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
调用的时候
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper , "width", 500).setDuration(5000).start();
这样就能正常展示咱们想要的动画作用,目标是wrapper ,它确实为width特点供给了get/set办法。动画内部在履行过程中会拿到传给它的特点,然后用反射去调用它的get/sset办法。可是怎么说呢,具体的特点改动的算法仍是要你去手动写。
办法3:
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
float fraction = animator.getAnimatedFraction();
view.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
view.requestLayout();
}
});
这个办法便是对动画进行监听,每监听到一帧的时候再做具体的操作。可以看到这儿也没写get/set办法,大约就那么一个意思就得了,也不是非得说一定要这样写,或者不必这两种办法也还有其它办法能完结。 总之,特点的具体的变化,是需求咱们自己去写逻辑(假如要完结自定义的作用),而ObjectAnimator、ValueAnimator这些特点广告的类,能告诉咱们这个动画履行的整个过程,简略的说便是咱们知道播放到哪一帧,就能做到在这一帧做什么作用。 这两个Demo也是我觉得这章最有意思的地方。
二. 自己完结一个自定义动画
光看必定是3天忘,自己动手写个简略的自定义动画作用。 我弄一个圆形View,可以拖动它,点击下去的时候圆会扩大并变成正方形,然后移动,松开时正方形会缩小并变回圆,而且咱们要让圆变成正方形不是秒变,要有个突变的作用(只完结了简略的突变作用,由于懒得去计算)。 终究的作用也没有做成gif,想看作用的直接仿制代码去试就行,不多。
####1. 制作view的初始方位 我这Demo再弥补一些细节的东西,仍是《Android 开发艺术探究》3.1.2 View的方位参数 不是什么难点,便是有些细节要注意,这个view的方位参数列举了3套 (1) Left, Right, Top, Bottom , 当成一个矩形看(圆也是矩形),便是4条边间隔父容器x\y轴的间隔。 (2) x, y 左上点的坐标 (3)translationX,translationY 间隔父View坐标的偏移量
我就直接贴代码讲吧。 先看Activity布局
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl_content"
android:padding="100px"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
这儿padding一个100px,是为了方便介绍这些方位,单位相同简略看,实践开发中必定最好用dp做单位。 然后看Activity代码
public class RsActivity extends Activity {
private FrameLayout flContent;
private RoundSquareView roundSquareView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rs);
initView();
}
private void initView(){
flContent = findViewById(R.id.fl_content);
roundSquareView = new RoundSquareView(this);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(150, 150);
roundSquareView.setLayoutParams(lp);
flContent.addView(roundSquareView);
roundSquareView.setX(100);
roundSquareView.setY(100);
}
@Override
protected void onResume() {
super.onResume();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.v("mmp", "getLeft "+roundSquareView.getLeft());
Log.v("mmp", "getTranslationX "+roundSquareView.getTranslationX());
Log.v("mmp", "getX "+roundSquareView.getX());
roundSquareView.setX(0);
}
}, 1000);
}
}
RoundSquareView是一个自定义View, 先不必管它。 第一个问题来了,为什么要写个延迟,重点啊,由于getLeft ,便是获取第一套的那4个特点,要在view制作完结之后才干获取到,否则获取到的会是0,加个延迟是为了确保制作完结(这仅仅Demo,实在开发中判别制作完结必定不能用延时这样玩) 看看显现成果
再看看打印成果
间隔顶部和左边都是200px,可是left打印是100,假如把父布局中的padding去掉的话,left会打印0,这便是第二个地方,使用Left的时候要注意padding,而且它不算translationX偏移的部分。 然后translationX也打印的是100,他也不算是padding的部分。 最终getX是200,他是正常的相对于父布局的部分间隔,不受任何影响。 也证实了x = left + translationX 这儿仅仅为了介绍一些细节上的东西而已,onResume里边的代码对咱们要完结的功用没有任何含义,所以使用时请屏蔽掉。
2. 自定义View
看看代码
public class RoundSquareView extends View {
private float oldRawX = 0;
private float oldRawY = 0;
private int currentValue = 0;
private boolean isExpand;
public RoundSquareView(Context context) {
super(context);
}
public RoundSquareView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RoundSquareView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(1);
int w = getWidth() /2;
int h = getHeight() /2;
if (!isExpand) {
// 非扩展状态下的制作
canvas.drawCircle(w, h, w + currentValue , paint);
}else {
// 扩展状态下的制作
canvas.drawCircle(w, h, w + (100 - currentValue) , paint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
isExpand = false;
expand();
oldRawX = event.getRawX();
oldRawY = event.getRawY();
return true;
case MotionEvent.ACTION_MOVE:
moveView(event.getRawX(), event.getRawY());
return true;
case MotionEvent.ACTION_UP:
oldRawX = 0;
oldRawX = 0;
isExpand = true;
narrow();
return true;
}
return super.onTouchEvent(event);
}
@Override
public boolean performClick() {
return super.performClick();
}
private void moveView(float rawX, float rawY){
// 计算偏移量
float offsetX = rawX - oldRawX;
float offsetY = rawY - oldRawY;
// 更改方位
setX(getX()+offsetX);
setY(getY()+offsetY);
// 更新方位
oldRawX = rawX;
oldRawY = rawY;
}
/**
* 扩展
*/
private void expand(){
int w = getWidth();
int h = getHeight();
ViewGroup.LayoutParams lp = this.getLayoutParams();
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentValue = (int) animation.getAnimatedValue();
Log.v("mmp", "动画进展 "+currentValue);
lp.width = w + (w/100 * currentValue);
lp.height = h + (h/100 * currentValue);
RoundSquareView.this.setLayoutParams(lp);
}
});
valueAnimator.setDuration(300).start();
}
/**
* 缩小
*/
private void narrow(){
int w = getWidth();
int h = getHeight();
ViewGroup.LayoutParams lp = this.getLayoutParams();
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentValue = (int) animation.getAnimatedValue();
Log.v("mmp", "动画进展 "+currentValue);
lp.width = w - (w/200 * currentValue);
lp.height = h - (h/200 * currentValue);
RoundSquareView.this.setLayoutParams(lp);
}
});
valueAnimator.setDuration(300).start();
}
}
自定义View,在draw中用Paint画个圆,这个,这个没什么好说的,都能看懂,最终
if (!isExpand) {
// 非扩展状态下的制作
canvas.drawCircle(w, h, w + currentValue , paint);
}else {
// 扩展状态下的制作
canvas.drawCircle(w, h, w + (100 - currentValue) , paint);
}
先不必管,先看成
canvas.drawCircle(w, h, w, paint);
这样就画成一个圆了。 然后写onTouchEvent让view随手指移动而移动。记载改动前的手指的点击方位oldRawX,oldRawY,去获取当时的方位减去旧的方位就能得到一个偏移量,然后再用这个偏移量去改动x和y特点,这是最简略的view随手指移动的方法。 然后在按下时调用expand()做扩大的特点动画,再抬起时调用narrow()做缩小的特点动画。
这儿是用了上面的办法3去完结特点动画,这儿离就不多说了,定义了一个变量isExpand来记载当时是否处于扩大的状态。 最终,这个Demo仅仅简略的完结动画的作用,会存在一些问题,比如说在扩大动画的履行过程中抬起,这些都没做处理,时刻问题,就不打算花太多时刻在这个Demo上。
文章出自www.jianshu.com/p/ad80cea0a… 当然也是自己写的,是同一个号,仅仅最近打算转渠道
这是很早之前写的文章,当时仍是有点年轻,技能差点火候。这儿更正一下,onResume那不必延时,用post就行