《Android群英传》读书笔记

650 阅读30分钟

Android群英传

五、AndroidScroll分析

1.滑动效果如何产生的

1).Android 坐标系

含义: 将屏幕最左上角的顶点作为Android坐标系的远点,这个点向右是X轴正方向,这个点向下是Y轴正方向。

系统提供了getLocationOnScreen(int location[])的方法来获取Android 坐标系中点的位置,另外在触控事件中使用getRawX()、getRawY()方法获得的坐标统一是Android 坐标系中的坐标

2).视图坐标系

含义: 描述了子视图在父视图中的位置关系

在触控事件中,通过getX(),getY()所获得的坐标就是视图坐标系中的坐标

3).触控事件——MotionEvent

通常情况下,我们会在onTouchEvent(MotionEvent event)方法中通过event.getAction()方法来获取触控事件的类型

  • View提供的获取坐标的方法

    getTop():获取到的是View自身的顶点到其父布局顶边的距离

    getLeft():获取到的是View自身的左边到其父布局左边的距离

    getRight():获取到的是View自身的右边到其父布局左边的距离

    getLeft():获取到的是View自身的底边到其父布局顶边的距离

  • MotionEvent提供的方法

    getX():获取点击事件距离控件左边的距离,即视图坐标

    getY():获取点击事件距离控件顶边的距离,即视图坐标

    getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标

    getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标

2.实现滑动的七种方法

1).layout方法

  • 在每次回调onTouchEvent的时候,我们都获取下触摸点的坐标
  • 在ACTION_DOWN事件中记录触摸点的坐标
  • 最后,在ACTION_MOVE事件中计算偏移量,并将偏移量作用到Layout方法中,在目前Layout的left,top,right,bottom基础上,增加计算出来的偏移量
@Override
public boolean onTouchEvent(MotionEvent event){
  int x = (int)event.getX();
  int y = (int)event.getY();
  switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
      //记录触摸点坐标
      lastX = x;
      lastY = y;
      break;
    case MotionEvent.ACTION_MOVE:
     	//计算偏移量
      int offsetX = x - lastX;
      int offsetY = y - lastY;
      //在当前left,top,right,bottom的基础上加上偏移量
      layout(getLeft() + offsetX,
            getTop() + offsetY,
            getRight() + offsetX,
            getBottom() + offsetY);
      break;
  }
  return true;
}

注意点: 如果使用绝对坐标系,在每次执行完ACTION_MOVE的逻辑后一定要重新设置初始坐标系,这样才能准确的获取偏移量

2).offsetLeftAndRight()与offsetTopAndBottom()

这个方法相当于系统提供的一个对左右、上下移动的API的封装

//同时对left和right进行偏移
offsetLeftAndRight(offsetX)
//同时对left和right进行偏移
offsetTopAndBottom(offsetY)

3).LayoutParams

必须要有一个父布局,系统才能获取LayoutParams,通过getLayoutParams()获取时,需要根据View所在的父布局的类型来设置不同的类型,例如在LinearLayout中使用LinearLayout.LayoutParams,在RelativeLayout中用RelativeLayout.LayoutParams

也可以直接使用ViewGroup.MarginLayoutParams,这样不需要考虑父布局的类型

LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)getLayoutParams();
params.leftMargin = getLeft() + offsetX;
params.topMargin = getTop() +offsetY;
setLayoutParams(layoutParams);

4).scrollTo与scrollBy

scrollTo(x,y)表示移动到一个具体的坐标点(x,y)

scrollBy(dx,dy)表示移动的增量未dx,dy

这两个方法移动的是View的content,如果需要实现跟随手指的移动而滑动的效果,必须将偏移量设置为负值

5).Scroller

  • 初始化Scroller

  • 重写computeScroll()方法,实现模拟滑动,这个方法实际上就是使用的scrollTo方法

    @Override
    public void computeScroll(){
      super.computeScroll();
      //判断Scroller是否执行完毕
      if(mScroller.computeScrollOffset()){
        ((View)getParent()).scrollTo(
        				mScroller.getCurrX(),
        				mScroller.getCurrY());
        //通过重绘来不断调用computeScroll
        invalidate();
      }
    }
    
  • startScroll开启模拟过程,有两个重载方法,一个带时间,其他的参数表示起始坐标和偏移量

    startScroll(int startX,int startY,int dx,int dy,int duration)

    startScroll(int startX,int startY,int dx,int dy)

6).属性动画

具体直接参考第七章

7).ViewDragHelper

  • 初始化ViewDragHelper

    通常定义在一个VIewGroup的内部,并且通过其静态工厂方法进行初始化,第一个参数是要监听的view,通常需要的就是一个ViewGroup,即parentView,第二个参数就是一个Callback回调

    mViewHelper = ViewDragHelper.create(this,callback);
    
  • 拦截事件

    @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return mViewHelper.shouldInterceptTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            //将触摸事件传递给ViewDragHelper,此操作必不可少
            mViewHelper.processTouchEvent(event);
            return true;
        }
    
  • 处理computeScroll()

    ViewDragHelper内部也是用Scroller来实现平滑移动的,同样需要重写computeScroll()方法

    @Override
        public void computeScroll() {
            if (mViewHelper.continueSettling(true)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
    
  • 处理Callback

     private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
            //何时开始检测触摸事件
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                //如果当前触摸的child是mMainView时开始检测
                return mMainView = child;
            }
    
            @Override
            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                return 0;
            }
    
            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                return left;
            }
       
        		//拖动结束后调用
            @Override
            public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
                //手指抬起后缓慢移动到指定位置
                if (mMainView.getLeft() < mWidth / 2) {
                    //关闭菜单
                    //相当于Scroller的startScroll方法
                    mViewHelper.smoothSlideViewTo(mMainView, 0, 0);
                    ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                } else {
                    //打开菜单
                    mViewHelper.smoothSlideViewTo(mMainView, mWidth, 0);
                    ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                }
            }
        };
    

六、绘图机制与处理技巧

1.屏幕的尺寸信息

1).屏幕参数

  • 屏幕大小:指屏幕对角线的长度
  • 分辨率:指手机屏幕的像素点的个数
  • PPI:每英寸像素,又被称为DPI

2).系统屏幕密度

密度 dpi mdpi hdpi xhdpi xxhdpi
密度值 120 160 240 320 480
分辨率 240*320 320*480 480*800 720*1280 1080*1920

3).独立像素密度dp

Android系统使用mdpi的屏幕作为标准,在这个屏幕上1px=1dp,在各种密度的屏幕上按照比例换算,比例如下

ldpi:mdpi:hdpi:xhdpi:xxhdpi = 3:4:6:8:12

4).单位转换

context.getResources().getDisplayMetrics().density可以获取到换算比例

也可以使用TypedValued类进行转换

public int dp2px(int dp){
  return (int)TypedValue.applyDimension(
    													TypedValue.COMPLEX_UNIT_DIP,
                              dp,
    													getResources().getDisplayMetrics());
}

2.2D绘图基础

Paint的属性和对应的功能:

  • setAntiAlias(); //设置画笔的锯齿效果
  • setColor(); //设置画笔的颜色
  • setARGB(); //设置画笔的A,R,G,B值
  • setAlpha(); //设置画笔的Alpha值
  • setTextSize(); //设置字体的尺寸
  • setStyle(); //设置画笔空心还是实心
  • setStrokeWidth(); //设置空心边框的宽度

Canvas的方法

  • DrawPoint,绘制点
  • DrawLine,绘制一条直线
  • DrawLines,绘制多条直线
  • DrawRect,绘制矩形
  • DrawRoundRect,绘制圆角矩形
  • DrawCircle,绘制圆
  • DrawArc,绘制弧形,扇形
  • DrawOval,绘制椭圆
  • DrawText,绘制文本
  • DrawPosText,在指定位置绘制文本
  • DrawPath,绘制路径

3.Android XML绘图

1).Bitmap

2).Shape

通过Shape可以在xml中绘制各种形状

3).Layer

可以通过layer,layer-list实现Photo中的图层效果,图片会依次叠加

4).Selector

作用在于实现静态绘图中的事件反馈,通过给不同的时间设置不同的图像,从而在程序中根据用户输入,返回不同的效果

4.Android绘图技巧

1).Canvas

  • Canvas.save();将之前所有已经绘制的图像保存起来
  • Canvas.restore();将在save()之后绘制的所有图像与save()之前的图像进行合并
  • Canvas.translate();将原点移动到(x,y),之后的绘图操作都将以(x,y)为原点执行,x,y代表的也是位移差,不是绝对坐标,也就是说如果之前是(10,10),那么translate(20,20)后不是移动到了(20,20)处,而是移动到了(30,30)处,位移累加效果
  • Canvas.rotate();旋转画布

2).Layer图层

通过saveLayer()方法、saveLayerAlpha()方法将一个图层入栈,使用restore()、restoreToCount()方法将一个图层出栈

5.图像处理之色彩特效处理

  • 色调——物体传播的颜色
  • 饱和度——颜色的纯度,从0(灰)到100%(饱和)来进行描述
  • 亮度——颜色的相对明暗程度

1).色彩矩阵分析

在Android 中,系统使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩斑斓的效果。

\left| \begin{matrix} a & b & c & d & e\\ f & g & h & i & j \\ k & l & m & n & o \\ p & q & r & s & t \end{matrix} \right|
\left| \begin{matrix}R\\G\\B\\A\\1\end{matrix}\right|
=
\left| \begin{matrix} aR & bG & cB & dA & e\\ fR & gG & hB & iA & j \\ kR & lG & mB & nA & o \\ pR & qG & rB & sA & t \end{matrix} \right|
=
\left|\begin{matrix}R1\\G1\\B1\\A1\end{matrix}\right|
  • 第一行的abcde值用来决定新的颜色值中的R--红色
  • 第二行的fghij值用来决定新的颜色值中的G--绿色
  • 第三行的klmno值用来决定新的颜色值中的B--蓝色
  • 第四行的pqrst值用来决定新的颜色值中的A--透明度
  • 矩阵中第五列ejot值分别用来决定每个分量中的offset,即偏移量

初始的矩阵正常如下显示

\left|\begin{matrix}1&0&0&0&0\\0&1&0&0&0\\0&0&1&0&0\\0&0&0&1&0\end{matrix}\right|

常用到的方法

a.改变偏移量

例如红色和绿色各自增加100,最后图片显示偏黄

\left|\begin{matrix}1&0&0&0&100\\0&1&0&0&100\\0&0&1&0&0\\0&0&0&1&0\end{matrix}\right|
b.改变颜色系数

例如绿色系数调为2倍,最后图片偏绿

\left|\begin{matrix}1&0&0&0&0\\0&2&0&0&0\\0&0&1&0&0\\0&0&0&1&0\end{matrix}\right|
c.改变光属性
  • 色调

    ColorMartix提供了setRotate(int axis,float degree)来帮助我们设置颜色的色调,第一参数,系统分别用0,1,2来表示RGB三种颜色的处理,第二个参数就是需要处理的值

  • 饱和度

    ColorMartix提供了setSaturatuin(float sat)方法来设置颜色的饱和度,参数即代表设置颜色的饱和度的值,当饱和度为0时,图像就变成灰度图像了

  • 亮度

    当三原色以相同的比例进行混合时,就会显示出白色,当亮度为0时,图像就变成全黑的了

2).Android颜色矩阵——ColorMatrix

调整颜色矩阵可以改变一幅图像的色彩效果,图像处理很大程度上就是在寻找处理图像的颜色矩阵

3).常用图像颜色矩阵处理效果

  • 灰度效果

    \left|\begin{matrix}0.33F&0.59F&0.11F&0&0\\0.33F&0.59F&0.11F&0&0\\0.33F&0.59F&0.11F&0&0\\0&0&0&1&0\end{matrix}\right|
  • 图像反转

    \left|\begin{matrix}-1&0&0&0&0\\0&-1&0&0&0\\0&0&-1&0&0\\0&0&0&1&0\end{matrix}\right|
  • 怀旧效果

    \left|\begin{matrix}0.393F&0.769F&0.189F&0&0\\0.349F&0.686F&0.168F&0&0\\0.272F&0.534F&0.131F&0&0\\0&0&0&1&0\end{matrix}\right|
  • 去色效果

    \left|\begin{matrix}1.5F&1.5F&1.5F&0&-1\\1.5F&1.5F&1.5F&0&-1\\1.5F&1.5F&1.5F&0&-1\\0&0&0&1&0\end{matrix}\right|
  • 高饱和度

    \left|\begin{matrix}1.438F&-0.122F&-0.016F&0&-0.03F\\-0.062F&1.378F&-0.016F&0&0.05F\\-0.062F&-0.122F&1.438F&0&-0.02F\\0&0&0&1&0\end{matrix}\right|

4).像素点分析

系统提供了Bitmap.getPixels()方法来帮助我们提取整个Bitmap中的像素点,并保存到一个数组中,如下

bitmap.getPixels(pixels,offset,stride,x,y,width,height)
  • pixels——接收位图颜色值的数组
  • offset——写入到pixels[]中的第一个像素索引值
  • stride——pixels[]中的行间距
  • x——从位图中读取的第一个像素的x坐标值
  • y——从位图中读取的第一个像素的y坐标值
  • width——从每一行中读取的像素宽度
  • height——读取的行数
bitmap.getPixels(oldPx,0,bm.getWidth(),0,0,width,height);
color = oldPx[i];
r = Color.red(color);
g = Color.green(color);
b = Color.blue(color);
a = Color.alpha(color);

可以对每个点进行颜色处理合成新像素点

最后将处理之后的像素点数组调用setPixels方法重新set给我们的Bitmap,达到处理图像的目的

5).常用图像像素点处理效果

  • 底片效果:对应的颜色分别用255减去初始值后再赋值

  • 老照片效果

    r1 = (int)(0.393*r + 0.769*g + 0.189*b);
    g1 = (int)(0.349*r + 0.686*g + 0.168*b);
    b1 = (int)(0.272*r + 0.534*g + 0.131*b);
    
  • 浮雕效果:若存在ABC三个像素点,要对B点对应的浮雕效果,代码如下

    B.r = C.r - B.r + 127;
    B.g = C.g - B.g + 127;
    B.b = C.b - B.b + 127;
    

6.图像处理之图形特效处理

1).Android变形矩阵——Matrix

默认的图像变换矩阵初始为

\left|\begin{matrix}1&0&0\\0&1&0\\0&0&1\end{matrix}\right|

矩阵变换规律:

\left|\begin{matrix}Scale\_X&Skew\_X&Trans\_X\\Scale\_Y&Skew\_Y&Trans\_Y\\0&0&1\end{matrix}\right|
  • A和E控制Scale——缩放变换
  • B和D控制Skew——错切变换
  • C和F控制Trans——平移变换
  • A、B、C、D共同控制Rotate——旋转变换
a.平移变换:将每个像素点都进行平移交换
\left|\begin{matrix}X_1\\Y_1\\1\end{matrix}\right|=\left|\begin{matrix}1&0&△x\\0&1&△y\\0&0&1\end{matrix}\right|\left|\begin{matrix}X_0\\Y_0\\1\end{matrix}\right|
b.旋转变换:指一个点围绕一个旋转中心旋转到一个新的点
\left|\begin{matrix}X_1\\Y_1\\1\end{matrix}\right|=\left|\begin{matrix}cosθ&-sinθ&0\\sinθ&cosθ&0\\0&0&1\end{matrix}\right|\left|\begin{matrix}X_0\\Y_0\\1\end{matrix}\right|

如果以任意点O为旋转中心来进行旋转变换,通常需要下面三步骤:

  • 将坐标原点平移到O点
  • 使用前面讲的以坐标原点为中心的旋转方法进行旋转变换
  • 将坐标原点还原
c.缩放变换:将每个点的坐标都进行相同比例的缩放,最终就会形成让整个图像缩放的效果
\left|\begin{matrix}X_1\\Y_1\\1\end{matrix}\right|=\left|\begin{matrix}K_1&0&0\\0&K_2&0\\0&0&1\end{matrix}\right|\left|\begin{matrix}X_0\\Y_0\\1\end{matrix}\right|
d.错切变换:将所有点的X坐标(或者Y坐标)保持不变,对应的Y坐标(或者X坐标)按照比例进行平移,且平移的大小和该点到X轴(或者Y轴)的垂直距离成正比
\left|\begin{matrix}X_1\\Y_1\\1\end{matrix}\right|=\left|\begin{matrix}1&K_1&0\\K_2&1&0\\0&0&1\end{matrix}\right|\left|\begin{matrix}X_0\\Y_0\\1\end{matrix}\right|

2).像素块分析

drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
        @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
        @Nullable Paint paint) 

该方法的关键参数如下:

  • bitmap:将要扭曲的图像
  • meshWidth:需要的横向网格数目
  • meshHeight:需要的纵向网格数目
  • verts:网格交叉点坐标数组
  • vertOffset:verts数组中开始跳过的(x,y)坐标对的数目

7.图像处理之画笔特效处理

1).PorterDuffXfermode

类似于数学中的交集和并集,控制的是两个图像间的混合显示模式,dst是先画的图形,src是后画的图形

mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1);
mOut = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mOut);
mPaint = new Paint();
mPaint.setAntiAlias(true);
canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), 180, 180, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mBitmap,0,0,mPaint);

2).Shader

  • BitmapShaper——位图Shader
  • LinearGradient——线性Shader
  • RadialGradient——光束Shader
  • SweepGradient——梯度Shader
  • ComposeShader——混合Shader

除了BitmapShader,其余的都是渐变渲染效果。BitmapShader产生的是一个图像,通过Paint对 画布进行指定Bitmap填充,填充有以下几种模式可以选择

  • CLAMP拉伸——拉伸的是图片最后的一个像素,不断重复
  • REPEAT重复——横向、纵向不断重复
  • MIRROR镜像——横向不断翻转重复,纵向不断翻转重复

3).PathEffect

  • CornerPathEffect

    CornerPathEffect则可以将路径的转角变得圆滑,CornerPathEffect的构造方法只接受一个参数radius,意思就是转角处的圆滑程度

  • DiscretePathEffect

    DiscretePathEffect(离散路径效果)相对来说则稍微复杂点,其会在路径上绘制很多“杂点”的突出来模拟一种类似生锈铁丝的效果。其构造方法有两个参数:

    第一个参数指定这些突出的“杂点”的密度,值越小杂点越密集;

    第二个参数则是“杂点”突出的大小,值越大突出的距离越大反之反之。

  • DashPathEffect

    DashPathEffect可以用来绘制虚线,主要有两个参数

    第一个参数为数组用来设置各个点之间的间隔,按照实线虚线交叉设置,后面绘制虚线会重复这样的间隔

    第二个参数phase用来控制绘制时数组的一个偏移量,可以通过这个设置来实现路径的动态效果

  • PathDashPathEffect

    PathDashPathEffect和DashPathEffect类似,不过功能更多,可以设置点的图形,比如方形点和圆形点

  • ComposePathEffect和SumPathEffect

    ComposePathEffect和SumPathEffect都可以用来组合两种路径效果,就是把两种效果二合一。唯一不同的是组合的方式:

    ComposePathEffect(PathEffect outerpe, PathEffect innerpe)会先将路径变成innerpe的效果,再去复合outerpe的路径效果,即:outerpe(innerpe(Path));

    SumPathEffect(PathEffect first, PathEffect second)则会把两种路径效果加起来再作用于路径。

8.View之孪生兄弟——SurfaceView

1).SurfaceView与View的区别

  • View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,比如频繁刷新
  • View在主线程中对画面进行刷新,SurfaceView通常会通过一个子线程来进行页面的刷新
  • View在绘图时没有使用双缓冲机制,SurfaceView在底层就已经实现了双缓冲机制

2).SurfaceView的使用

  • 创建SurfaceView

    创建自定义的SurfaceView需要继承自SurfaceView ,并且需要实现两个接口SurfaceHolder.Callback(分别对应创建、改变和销毁)和Runnable

  • 初始化SurfaceView

    在自定义SurfaceView的构造方法中,需要进行初始化,通常需要定义以下三个成员变量

    //SurfaceHolder
    private SurfaceHolder mHolder;
    //用于绘图的Canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;
    

    初始化方法就是对SurfaceHolder进行初始化,可以通过以下代码初始化一个SurfaceHolder对象,并注册SurfaceHolder的回调方法

    mHolder = getHolder();
    mHolder.addCallback(this);
    
  • 使用SurfaceView

    在SurfaveView回调方法surfaceCreated方法中开启子线程进行绘制,子线程使用一个while(mIsDrawing)来循环不停的进行绘制,在绘制的具体逻辑中,通过lockCanvas()方法获得Canvas对象进行绘制(该对象还是继续上次的Canvas对象,如果需要擦除,可以调用drawColor()方法进行清屏操作),并通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交

七、动画机制与使用技巧

1.Android View 动画框架

动画框架产生的动画不能改变事件相应的位置

1).透明度动画

为视图增加透明度的变换动画

AlphaAnimation aa = new AlphaAnimation(0,1);
aa.setDuration(1000);
view.startAnimation(aa);

2).旋转动画

为视图增加旋转的变换动画,参数分别为旋转的起始角度和旋转中心点的坐标

RotateAnimation ra1 = new RotateAnimation(0,360,100,100);
ra1.setDuration(1000);
view.startAnimation(ra1);

//设置旋转参考系为自身中心点
RotateAnimation ra2 = new RotateAnimation(0,360,
                                          RotateAnimation.RELATIVE_TO_SELF,0.5F,
                                          RotateAnimation.RELATIVE_TO_SELF,0.5F);
ra2.setDuration(1000);
view.startAnimation(ra2);

3).位移动画

为视图移动时增加位移动画

TranslateAnimation ta = new TranslateAnimation(0,200,0,300);
ta.setDuration(1000);
view.startAnimation(ta);

4).缩放动画

为视图的缩放增加动画效果

ScaleAnimation sa = new ScaleAnimation(0,2,0,2);
sa.setDuration(1000);
view.startAnimation(sa);

//设置以自身中心为缩放的中心点
ScaleAnimation sa2 = new ScaleAnimation(0,1,0,1
                                       Animation.RELATIVE_TO_SELF,0.5F,
                                       Animation.RELATIVE_TO_SELF,0.5F);
sa2.setDuration(1000);
view.startAnimation(sa2);

5).动画集合

通过AnimationSet,可以将动画以组合的形式展现出来,通过设置AnimationListener,可以针对动画的开始,结束和重复事件作出不同的处理

2.Android属性动画分析

在Animator框架中使用最多的就是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值,使用多个ObjectAnimator组合AnimatorSet形成一个动画。

1).ObjectAnimator

创建一个ObjectAnimator对象只需要通过他的静态工厂类之间返回一个ObjectAnimator对象。参数包括一个对象和对象的属性名字,这个属性必须有get和set函数,内部会通过反射机制来调用set函数修改对象属性值

ObjectAnimator anim = ObjectAnimator.ofFloat(view,"translationX",300);
anim.setDuration(300);
anim.start();

常用的可以直接使用属性动画的属性值:

  • translationX和translationY:这两个属性作为一种增量来控制着View对象从它布局容器的左上角坐标开始的位置
  • rotation、rotationX和rotationY:这三个属性控制着View对象围绕支点进行2D和3D旋转
  • scaleX和scaleY:这两个属性控制着View对象围绕它的支点进行2D缩放
  • piovtX和pivotY:这两个属性控制着View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,支点的位置就是View对象的中心点
  • x和y:这是两个简单实用的属性,描述了View对象在它的容器中的最终位置,是最初的左上角坐标和translationX、translationY值的累计和
  • alpha:它表示View对象的alpha透明度。默认值是1(不透明),0代表完全透明

2).PropertyValuesHolder

类似视图动画中的AnimationSet,在属性动画中,如果针对同一个对象的多个属性,要同时作用多种动画,可以使用PropertyValuesHolder来实现。

PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("tanslationX",300f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f,0,1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY",1f,0,1f);
ObjectAnimator.ofPropertyValuesHolder(view,pvh1,pvh2,pvh3).setDuration(1000).start()

3).ValueAnimator

ValueAnimator本身不提供任何动画效果,更像是一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程。在AnimatorUpdateListener中监听数值的变换,从而完成动画的变换

4).动画事件的监听

一个完整的动画具有Start、Repeat、End、Cancel四个过程,通过Android提供了接口,可以很方便的监听四个事件

ObjectAnimator anim = ObjectAnimator.ofFloat(view,"alpha",0.5f);
        anim.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }
            @Override
            public void onAnimationEnd(Animator animation) {
            }
            @Override
            public void onAnimationCancel(Animator animation) {
            }
            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        anim.start();

如果只对部分过程监听,可以使用AnimatorListenerAdapter进行监听

5).AnimatorSet

AnimatorSet可以通过playTogether()、playSequentially()、animSet.play().with()、play().with()、play().before()、play().after()这些方法控制多个动画的协同工作方式,从而做到对动画播放顺序的精确控制。

6).在XML中使用属性动画

属性动画同视图动画一样,也可以直接写在XML文件中,在res/animator目录下,如下

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="scaleX"
    android:valueFrom="1.0"
    android:valueTo="2.0"
    android:valueType="floatType">
</objectAnimator>

在程序中使用方式如下:

public void scaleX(View view){
  Animator anim = AnimatorInflater.loadAnimator(this,R.animator.scalex);
  anim.setTarget(mView);
  anim.start();
}

7).View的animate方法

可以认为是属性动画的简写方式,如下

view.animate()
                .alpha(0)
                .y(300)
                .setDuration(500)
                .withStartAction(new Runnable() {
                    @Override
                    public void run() {
                        
                    }
                })
                .withEndAction(new Runnable() {
                    @Override
                    public void run() {
                        
                    }
                })
                .start();

3.Android布局动画

在ViewGroup的XML中,可以用一下代码打开布局动画,显示默认的过渡效果,无法替换

android:animateLayoutChanges="true"

也可以通过使用LayoutAnimationController类来自定义一个子View的过渡效果

LinearLayout ll = findViewById(R.id.ll);
ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1);
sa.setDuration(2000);
LayoutAnimationController lac = new LayoutAnimationController(sa, 0.5F);
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
ll.setLayoutAnimation(lac);

第一个参数是需要作用的动画,第二个参数,是每个子View显示的delay时间,时间不为0时,可以设置子View显示的顺序:

  • LayoutAnimationController.ORDER_NORMAL——顺序
  • LayoutAnimationController.ORDER_RANDOM——随机
  • LayoutAnimationController.ORDER_REVERSE——反序

4.Interpolators(插值器)

在ValueAnimator中使用插值器可以直接调用setInterpolator(TimeInterpolator value)函数,可以根据需求自己定义插值器,只需要实现TimeInterpolator接口就行

public class MyInterpolator implements TimeInterpolator {
    @Override
    public float getInterpolation(float input) {
        return 1 - input;
    }
}

5.自定义动画

自定义动画需要继承Animation类,然后实现applyTransformation(float interpolatedTime, Transformation t)方法就可以,正常情况下还需要覆盖父类的initialize来实现一些初始化工作

  • 第一个参数interpolatedTime就是插值器的时间因子,这个因子是由动画当前完成的百分比和当前时间所对应的插值所计算得到的,取值范围为0到1.0

  • 第二个参数Transformation是矩阵的封装类,一般可以使用这个类来获得当前的矩阵对象

    final Matrix matrix = t.getMatrix();
    

例如实现电视机的关闭效果

//mCenterWidth,mCenterHeight是缩放点的中心
matrix.preScale(1,1-interpolatedTime,mCenterWidth,mCenterHeight);

6.SVG矢量动画机制

  • 可伸缩矢量图形(Scalable Vector Graphics)
  • 定义用于网络的基于矢量的图形
  • 使用XML格式定义图形
  • 图像在放大或者改变尺寸的情况下其图形质量不会有所损失
  • 万维网联盟的标准
  • 与诸如DOM和XSL之类的W3C标准是一个整体

1).<path>标签

标签pathData属性支持的指令有以下几种

  • M=moveto(M X,Y):将画笔移动到指定的坐标位置,但未发生绘制
  • L=lineto(L X,Y):画直线到指定的坐标位置
  • H=horizontal lineto(H X):画水平线到指定的X坐标位置
  • V=vertical lineto(V Y):画垂直线到指定的Y坐标位置
  • C=curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝塞曲线
  • S=smooth curveto(S X2,Y2,ENDX,ENDY):三次贝塞曲线(将上一条的指令终点作为这条指令的起始点)
  • Q=quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次贝塞曲线
  • T=smooth quadratic Belzier curveto(T ENDX,ENDY):映射前面路径后的终点
  • A=elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线
  • Z=closepath():关闭路径

使用时需要注意,指令可以大小写,大写表示绝对定位,参照全局坐标系,小写表示相对定位,参照父容器的坐标系;指令和数据间的空格可以省略;同一个指令出现多次可以只用一个

八、Activity与Activity调用栈分析

1.Activity

Activity的形态

  • Active/Running

    这时候,Activity处于Activity 栈的最顶层,可见并与用户进行交互

  • Paused

    Activity失去焦点时为Paused状态,只是失去了与用户交互的能力,所有状态信息,成员变量都还保持着

  • Stopped

    此时不再可见,依然保持了所有状态信息和成员变量

  • Killed

    被系统回收掉或者没有创建的时候就是这个状态

Activity生命周期如下:

2.Android任务栈

当一个App启动时,如果当前环境中不存在该App的任务栈,那么系统就会创建一个任务栈。此后,这个App所启动的Activity都将在这个任务栈中你那个被管理,这个栈也被称为一个Task

3.Activity启动模式

1).standard

未指定的Activity都以这个模式启动,每次都会创建新的实例

2).singleTop

在启动时,系统会判断当前启动的Activity是否处于栈顶,如果在,不创建新的,直接引用这个Activity,如果不处于则创建新的Activity

3).singleTask

检测整个Activity栈中是否存在当前需要启动的Activity,如果存在,则将该Activity置于栈顶,以上的Activity全部销毁,会调用该Activity的on new Intent()方法,只有一个实例

4).singeInstance

单独一个栈,并且只有单独一个实例

4.Intent Flag启动模式

通过设置Intent的Flag来设置一个Activity的启动模式,常用的有:

  • Intent.FLAG_ACTIVITY_NEW_TASK

    使用一个新的Task来启动一个Activity,但启动的每个Activity都将在一个新的Task中,通常用于从Service启动Activity

  • Intent.FLAG_ACTIVITY_SINGLE_TOP

    与指定launchMode为singleTop效果相同

  • Intent.FLAG_ACTIVITY_CLEAR_TOP

    与指定launchMode为singleTask效果相同

  • Intent.FLAG_ACTIVITY_NO_HISTORY

    使用这种模式启动Activity,当该Activity启动其他Activity后,就会消失,不会保留在栈中

5.清空任务栈

可以在AndroidMainifest文件的<activity>标签中使用以下几种属性来清理任务栈

  • clearTaskOnLaunch:每次返回该Activity时,都将之上的所有Activity清除
  • finishOnTaskLaunch:离开该Activity所处的Task,那么用户再返回时,该Activity会被finish掉
  • alwaysRetainState:如果将Activity的这个属性设置为True,那么该Activity所在的Task将不接受任何清理命令,一直保持当前Task状态

九、Android系统信息与安全机制

1.Android系统信息获取

1).android.os.Build

  • Build.BOARD // 主板
  • Build.CPU_ABI // cpu指令集
  • Build.DEVICE // 设备参数
  • Build.DISPLAY // 显示屏参数
  • Build.FINGERPRINT // 硬件名称
  • Build.HOST // Host值
  • Build.ID // 修订版本列表
  • Build.MANUFACTURER // 硬件制造商
  • Build.MODEL // 版本
  • Build.PRODUCT // 手机制造商
  • Build.TAGS // 描述build的标签
  • Build.TIME // 编译时间
  • Build.TYPE // builder类型
  • Build.USER // User名

2).SystemProperty

通过System.getProperty("XXX")可以获取许多系统配置属性值和参数,常用的如下

  • os.version // OS版本
  • os.name // OS名称
  • os.arch // OS架构
  • user.home // Home属性
  • user.name // Name属性
  • user.dir // Dir属性
  • user.timezone // 时区
  • path.separator // 路径分隔符
  • line.separator // 行分隔符
  • file.separator // 文件分隔符
  • java.vendor.url //Java vendor URL 属性
  • java.class.path // Java Class 路径
  • java.class.version // Java Class 版本
  • java.vendor // Java 版本
  • java.home // Java Home 属性

3).Android 系统信息实例

在system/build.prop文件中,包含很多RO属性值,在/proc目录下,可以看到更多的系统信息

2.Andoird Apk 应用信息获取之PackageManager

1).PackageManager

在上图中,最里面的框代表整个Activity信息,系统提供了ActivityInfo类进行封装

最外面的框代表这个整个Mainifest文件中节点的信息,系统提供了PackageInfo来进行封装

Android系统提供了PackaaageManager来负责管理所有已安装的App

  • ActivityInfo

    封装了在Mainifest文件中<activity><\activity>和<receiver><\receiver>之间的所有信息,包括name、icon、label、launchmod等

  • ServiceInfo

    ServiceInfo与ActivityInfo类似,它封装了<service><\service>之间的所有信息

  • ApplicationInfo

    封装了<application><\application>之间的信息,而且还包含很多Flag,FLAG_SYSTEM表示为系统应用,FLAG_EXTERNAL_STORAGE表示为安装在SDCard上的应用,通过这些Flag,可以很方便的判断应用的类型

  • PackageInfo

    封装Mainifest文件的相关节点信息,还包含了所有的Activity、Service等信息

  • ResolveInfo

    封装的是包含<intent>信息的上一级信息,可以返回ActivityInfo、ServiceInfo等包含<intent>的信息,经常用来帮助我们找的那些包含特定Intent条件的信息

PackageManager可以通过调用各种方法,返回不同类型的Bean对象,经常使用以下方法

  • getPackageManager:通过调用这个方法返回一个PackageManager对象
  • getApplicationInfo:以getApplicationInfo的形式返回指定包名的ApplicationInfo
  • getApplicationIcon:返回指定包名的Icon
  • getInstalledApplications:以ApplicationInfo的形式返回安装的应用
  • getInstalledPackages:以PackageInfo的形式返回安装的应用
  • queryIntentActivities:返回指定intent的ResolveInfo对象、Activity集合
  • queryIntentServices:返回指定intent的ResolveInfo对象、Service集合
  • resolveActivity:返回指定intent的Activity
  • resolveService:返回指定intent的Service

3.Android Apk应用信息获取之ActivityManaager

ActivityManager封装了不少Bean对象

  • ActivityManager.MemoryInfo

    MemoryInfo有几个非常重要的字段:availMem--系统可用内存,totalMem--总内存,thresold--低内存的阈值,即区分是否低内存的临界值,lowMemory--是否处于低内存

  • Debug.MemoryInfo

    用于统计进程下的内存信息

  • RunningAppProcessInfo

    运行进程的信息,存储的字段是进程相关的信息,processName--进程名,pid--进程pid,uid--进程uid,pkgList--该进程下的所有包

  • RunningServiceInfo

    用于封装运行的服务信息,包含了一些服务进程的信息,同时还有一些其他信息,activiteSince--第一次被激活的时间,方式,foreground--服务是否在后台执行

4.解析Packages.xml获取系统信息

在系统初始化的时候,packageManager的底层实现类PackageManagerService会去扫描系统中的一些特定的目录,并解析其中的Apk文件。同时,Android把获得的应用信息保存到XML文件中,当系统中的apk安装,删除,升级时,也会被更新,位于/data/system/目录下的--packages.xml文件

  • <permissions>标签

    定义了目前系统中的所有权限,并分为两类:系统定义的(package属性为Android)和Apk定义的(package属性为Apk的包名)

  • <package>标签

    package标签代表了一个Apk的属性,各节点的含义如下所示

    name:Apk的包名

    codePath:Apk的安装路径,主要有/system/app(存放系统Apk或者是厂商定制Apk)和/data/app/(存放用户安装的第三方Apk)两种

    userId:用户ID

    version:版本号

  • <perms>标签

    对应Apk的AndroidMainifest文件中的<uses-permission>标签,记录Apk的权限信息

5.Android安全机制

1).Android安全机制简介

  • 代码安全机制——代码混淆proguard
  • 应用接入权限控制——AndroidMainifest文件权限声明、权限检查机制
  • 应用签名机制——数字证书
  • Linux内核层安全机制——Uid、访问权限控制
  • Android虚拟机沙箱机制——沙箱隔离

2).Android系统安全隐患

  • 代码漏洞
  • Root风险
  • 安全机制不健全
  • 用户安全意识
  • Android开发原则与安全

3).Android Apk 反编译

  • apktool

    进入到apktool目录下,通过指令apktool d XX.apk,可以获得解压的文件夹,可以查看相关的反编译的代码,也可以再使用apktool b XX可以对文件夹重新打包生成apk

  • Dex2jar、jd-gui

    在apktool反编译后有一个dex文件,复制到dex2jar根目录下,然后执行如下命令:d2j-dex2jar.bat classes.dex,会在当前目录下生成一个jar文件,然后打开jd-gui,并选择file-open file,选择刚刚生成的classes-dex2jar.jar文件,就可以查看相关的源代码了

十、Android性能优化

1.布局优化

1).Android UI渲染机制

系统如果每次渲染的时间都在16ms之内,也就是60帧(1000/16)画面,这样UI界面会非常流畅

2).避免Overdraw

可以通过开启开发者选项中的检测工具查看,尽量增大蓝色的区域,减少红色区域

3).优化布局层级

Android中,系统对View进行测量、布局和绘制时,都是通过对View树的遍历来进行操作的,如果一个View树的高度太高,就会严重影响测量、布局和绘制的速度,因此优化布局的方法就是降低View树的高度

4).避免嵌套过多无用布局

  • 使用<include>标签重用Layout

  • 使用<ViewStub>实现View的延迟加载

    布局中的ViewStub默认不显示,通过findViewById找到后,然后调用setVisibility(View.VISIBLE)来显示,也可以调用ViewStub的inflate()来显示,两种显示效果一样,不同的是inflate()方法可以返回引用的布局,一旦ViewStub被设置为可见或者被inflate了,那么ViewStub就不存在了(调用两次inflate()方法将会报错),取而代之的是被inflate的布局,并将这个布局的ID重新设置为<ViewStub>中通过android:inflatedId属性指定的ID

2.内存优化

1).什么是内存

内存是指手机的RAM,包含以下几个部分

  • 寄存器(Registers)

    速度最快的存储场所,程序无法控制

  • 栈(Stack)

    存放基本类型的数据和对象引用,但对象本身不存在栈中,存放在堆中

  • 堆(Heap)

    堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由Java虚拟机的自动垃圾回收器(GC)来管理

  • 静态存储区域(Static Field)

    静态存储区域就是指在固定的位置存放应用程序运行时一直存在的数据,Java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量

  • 常量池

    JVM虚拟机必须为每个被装载的类型维护一个常量池,常量池就是该类型所用到常量的一个有序集合,包括直接常量(基本类型,String)和对其他类型、字段和方法的符号引用。

2).获取Android系统内存信息

直接用AS自带的Profiler可以查看

3).内存回收

Java创建了垃圾收集器线程来自动进行资源的管理,方便了开发人员 的工作,但是容易造成内存泄露

4).内存优化实例

  • Bitmap优化
    • 使用适当分辨率和大小的图片
    • 及时回收内存
    • 使用图片缓存
  • 代码优化
    • 对常量使用static修饰符
    • 使用静态方法,静态方法会比普通方法提高15%左右的访问速度
    • 减少不必要的成员变量,可以使用Android Lint工具检测
    • 减少不必要的对象,使用基础类型比使用对象更加节省资源,同时避免频繁创建短作用域的变量
    • 尽量不要使用枚举,少用迭代器
    • 对Cursor、Receiver、Sensor、File等对象,要非常注意对它们的创建、回收与注册、解注册
    • 避免使用IOC框架,大量使用反射会带来性能的下降
    • 使用RenderScript、OpenGL来进行非常复杂的绘图操作
    • 使用SurfaceView来替代View进行大量、频繁的绘图操作
    • 尽量使用视图缓存

3.Lint工具

Android Studio自带的Android代码提示工具

4.使用Android Studio的Profiler工具

在Android Studio3.6.2及以上版本可以使用自带的Profiler工具可以查看实时内存和网络请求等分析

5.使用TraceView工具优化App性能

1).生产TraceView日志的两种方法

  • 通过代码生成精确范围的TraceView日志

    通过调用Debug.startMethodTracing()方法开启监听,调用Debug.stopMethodTracing()方法结束监听,可以使用这两个方法来包围要监听的代码块,TraceView的日志将会保存到“/sdcard/dmtrace.trace”目录下,因此需要在Mainifest文件中增加WRITE_EXTRRNAL_STORAGE权限

    监听的内容执行完毕后,通过ADB命令将日志文件导出到本地:

    adb pull /sdcard/trace_log.trace/local/LOG/

  • 通过Android Studio查看TraceView日志

2).打开TraceView日志

对于导出的TraceView文件,可以使用SDK中的sdk\tools\traceview.bat工具来打开,或者通过ADM工具打开

3).分析TraceView日志

  • 时间轴区域:时间在走区域显示了不同线程在不同的时间段内的执行情况,在时间轴中,每一行都代表了一个独立的线程
  • Profile区域:
    • Incl CPU Time——某方法占用CPU的时间
    • Excl CPU Time——某方法本身(不包含子方法)占用CPU的时间
    • Incl Real Time——某方法真正执行的时间
    • Excl Real Time——某方法本身(不包括子方法)真正执行的时间
    • Calls+RecurCalls——调用次数+递归回调的次数