常规&高级UI编程(2) | 青训营笔记

108 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第6天

动画

帧动画

img

帧动画示例:

 <?xml version="1.0" encoding="utf-8"?> 
 <animation-list xmlns:android="http://schemas.android.com/apk/res/android" 
     android:oneshot="false">
     <item android:drawable="@drawable/ic_wifi_0" android:duration="100"/>
     <item android:drawable="@drawable/ic_wifi_1" android:duration="100"/>
     <item android:drawable="@drawable/ic_wifi_2" android:duration="100"/>
     <item android:drawable="@drawable/ic_wifi_3" android:duration="100"/> 
     <item android:drawable="@drawable/ic_wifi_4" android:duration="100"/> 
     <item android:drawable="@drawable/ic_wifi_5" android:duration="100"/> 
 </animation-list>
 ​
 ​
 private void playAnimation() {
     mImageView.setImageResource(R.drawable.frame_anim); 
     AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getDrawable(); 
     animationDrawable.start(); 
     ...
     animationDrawable.stop(); 
 }

img

补间动画

img

示例 :

 public void tweenedAnimation(View view) {   
     // 创建一个透明度动画,透明度从1渐变至0
     AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);  
     alphaAnimation.setDuration(3000);    
 ​
     // 创建一个旋转动画,从0度旋转至360度
     RotateAnimation rotateAnimation = new RotateAnimation(0, 360, 
         Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);  
     rotateAnimation.setDuration(3000);    
 ​
     ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0.5f, 1, 0.5f,
         Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
     scaleAnimation.setDuration(3000);    
 ​
     TranslateAnimation translateAnimation = new TranslateAnimation(
         Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, 
         Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);    
     translateAnimation.setDuration(3000);   
 ​
     // 组合上述4种动画
     AnimationSet animationSet = new AnimationSet(true);    
     animationSet.addAnimation(alphaAnimation);    
     animationSet.addAnimation(rotateAnimation);   
     animationSet.addAnimation(scaleAnimation);    
     animationSet.addAnimation(translateAnimation);   
     view.startAnimation(animationSet);
 }

img

差值器示例: img

属性动画

img

属性动画示例:

 private void startObjectAnimatorSet() {
 ​
      // 创建一个ObjectAnimator,将mImageView的scaleX属性值从1变化到0.5
     Animator scaleXAnimator = ObjectAnimator.ofFloat(mImageView, "scaleX", 1, 0.5f); 
     scaleXAnimator.setDuration(2000);  
 ​
      // 创建一个ObjectAnimator,将mImageView的scaleY属性值从1变化到0.5
     Animator scaleYAnimator = ObjectAnimator.ofFloat(mImageView, "scaleY", 1, 0.5f); 
     scaleYAnimator.setDuration(2000);  
 ​
      // 创建一个ObjectAnimator,将mImageView的rotationX属性值从0变化到360
     Animator rotationXAnimator = ObjectAnimator.ofFloat(mImageView, "rotationX", 0, 360);
     rotationXAnimator.setDuration(2000); 
 ​
      // 创建一个ObjectAnimator,将mImageView的rotationY属性值从0变化到360
     Animator rotationYAnimator = ObjectAnimator.ofFloat(mImageView, "rotationY", 0, 360);
     rotationYAnimator.setDuration(2000); 
 ​
     // 组合上述4种动画
     AnimatorSet animatorSet = new AnimatorSet(); 
     animatorSet.play(scaleXAnimator).with(scaleYAnimator)
     .before(rotationXAnimator).after(rotationYAnimator); 
     animatorSet.start(); 
 }

img

动画总结

img

两类动画的根本区别在于:是否改变动画本身的属性

  • 视图动画:不改变动画的属性,在动画过程中仅对图像进行变换来达到动画效果。无论动画结果在哪,该View的位置和响应区域都是在原地,不会根据结果而移动;
  • 属性动画:改变了动画属性 因属性动画在动画过程中对动态改变了对象属性,从而达到了动画效果

自定义View

自定义 View示例img

创建View

 public class SwitchButton extends View implements Checkable {
 ​
     public SwitchButton(Context context) {
         super(context);
         init(context, null);
     }
 ​
     public SwitchButton(Context context, AttributeSet attrs) {
         super(context, attrs);
         init(context, attrs);
     }
 ​
     public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         init(context, attrs);
     }
 ​
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         init(context, attrs);
     }

处理View布局

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
     final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
     if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
         widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_WIDTH, MeasureSpec.EXACTLY);
     }
     if(heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST){
         heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_HEIGHT, MeasureSpec.EXACTLY);
     }
     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 }
 ​
 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
     float viewPadding = Math.max(shadowRadius + shadowOffset, borderWidth);
     height = h - viewPadding - viewPadding;
     width = w - viewPadding - viewPadding;
     ...
 }

绘制View

 @Override
 protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     //绘制白色背景的圆角矩形
     paint.setStrokeWidth(borderWidth);
     paint.setStyle(Paint.Style.FILL);
     paint.setColor(background);
     drawRoundRect(canvas, left, top, right, bottom, viewRadius, paint);
 ​
     //绘制关闭状态的边框
     paint.setStyle(Paint.Style.STROKE);
     paint.setColor(uncheckColor);
     drawRoundRect(canvas,left, top, right, bottom, viewRadius, paint);
     ...
     //绘制按钮左边绿色长条遮挡
     paint.setStyle(Paint.Style.FILL);
     paint.setStrokeWidth(1);
     drawArc(canvas, left, top, left + 2 * viewRadius, top + 2 * viewRadius,90, 180, paint);
     canvas.drawRect( left + viewRadius, top,viewState.buttonX,
     top + 2 * viewRadius,paint);
     ...
     //绘制按钮
     drawButton(canvas, viewState.buttonX, centerY);
 }

处理用户交互

 @Override
 public boolean onTouchEvent(MotionEvent event) {
     if(!isEnabled()) { return false; }
     switch (actionMasked){
         case MotionEvent.ACTION_DOWN:
            ...
             break;
         case MotionEvent.ACTION_MOVE:
             if(isPendingDragState()){ //在准备进入拖动状态过程中,可以拖动按钮位置
                 ...
             }else if(isDragState()){ //拖动按钮位置,同时改变对应的背景颜色
                ...
             }
             break;
         case MotionEvent.ACTION_UP:
             if(System.currentTimeMillis() - touchDownTime <= 300){ //点击时间小于300ms,认为是点击操作
                 toggle();
             }else if(isDragState()){ //在拖动状态,计算按钮位置,设置是否切换状态
                 ...
             }
             break;
         case MotionEvent.ACTION_CANCEL:
             removeCallbacks(postPendingDrag);
             break;
     }
     return true;
 }

处理动画

 // 初始化View时设置动画
 valueAnimator = ValueAnimator.ofFloat(0f, 1f); 
 valueAnimator.setDuration(effectDuration); 
 valueAnimator.setRepeatCount(0);
 valueAnimator.addUpdateListener(animatorUpdateListener);
 // 点击开关后启动动画
 valueAnimator.start();
 ​
 // 简单动画更新回调,触发View绘制
 private ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         float value = (Float) animation.getAnimatedValue();
         switch (animateState) {
             ...
             case ANIMATE_STATE_SWITCH:
                 viewState.buttonX = beforeState.buttonX + (afterState.buttonX - beforeState.buttonX) * value;
                 float fraction = (viewState.buttonX - buttonMinX) / (buttonMaxX - buttonMinX);
                 viewState.checkStateColor = (int) argbEvaluator.evaluate( fraction,uncheckColor,checkedColor);
                 viewState.radius = fraction * viewRadius;
                 viewState.checkedLineColor = (int) argbEvaluator.evaluate(fraction,Color.TRANSPARENT, checkLineColor);
                 break;
         }
         postInvalidate();
     }
 };

自定义View小结

img

课程总结

img

\