第六章 Android 绘图机制与屏幕适配

Android 群英传笔记
第一章Android体系与系统架构
第二章 Android开发工具及技巧
第三章 Android控件架构与事件拦截机制
第四章 ListView 使用技巧
第五章 Android Scroll 分析
第六章 Android 绘图机制与屏幕适配
第七章 Android 动画机制与使用技巧
第八章 Activity与Activity调用栈分析
第九章 Android 系统信息与安全机制
第十章 Android性能优化
本文出自:
http://www.jianshu.com/u/a1251e598483

屏幕适配的知识
  • 屏幕大小

指屏幕对角线的长度,通常使用 "寸"来度量 ,通常的4.7寸,5.5寸手机,现如今各家推出的 带刘海的手机,计算规则相对复杂点

  • 分辨率

分辨率是指实际屏幕的像素点个数,例如720X1280就是指屏幕的分辨率,宽有720个像素点,高有1280个像素点

  • PPI

每英寸像素又称为DPI,他是由对角线的的像素点数除以屏幕的大小所得,通常有400PPI就已经很6了

系统屏幕密度

每个厂商的安卓手机具有不同的大小尺寸和像素密度的屏幕,安卓系统如果要精确到每种DPI的屏幕,基本上是不可能的,因此系统定义了几个标准的DPI

第六章 Android 绘图机制与屏幕适配_第1张图片
Android手机分辨率
  • 独立像素密度dp

这是由于各种屏幕密度的不同,导致同样像素大小的长度,在不同密度的屏幕上显示长度不同,因此相同长度的屏幕,高密度的屏幕包含更多的像素点,在安卓系统中使用mdpi密度值为160的屏幕作为标准,在这个屏幕上,1px = 1dp,其他屏幕则可以通过比例进行换算,例如同样是100dp的长度,mdpi中为100px,而在hdpi中为150,我们也可以得出在各个密度值中的换算公式,在mdpi中 1dp = 1px, 在hdpi中, 1dp = 1.5px,在xhdpi中,1dp = 2px,在xxhdpi中1dp = 3px,由此可见,我们换算公式 l:m:h:xh:xxh = 3:4:6:8:12

  • 安卓手机对于每类手机屏幕大小都有一个相应的屏幕像素密度:
| 密度类型 | 代表的分辨率(px) | 屏幕像素密度(dpi)|
| ------------- |:-------------:|
| 低密度(ldpi) | 240x320 | 120 |
| 中密度(mdpi) | 320x480 | 160 |
| 高密度(hdpi) | 480x800 | 240|
| 超高密度(xhdpi) | 720x1280 | 320|
| 超超高密度(xxhdpi) | 1080x1920 | 480 |

  • 屏幕尺寸、分辨率、像素密度三者关系

一部手机的分辨率是宽x高,屏幕大小是以寸为单位,那么三者的关系是:

第六章 Android 绘图机制与屏幕适配_第2张图片
image
  • 密度无关像素

含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。
单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果
Android开发时用dp而不是px单位设置图片大小,是Android特有的单位
场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在480x800分辨率手机上设置应为240px;在320x480的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。

  • 独立比例像素

含义:scale-independent pixel,叫sp或sip
单位:sp

  • 单位换算

在程序中,我们可以非常方便地对一些单位的换算,下面的代码给出了一种换算的方法我们可以把这些代码作为工具类保存在项目中

package com.younger.testyounger;

import android.content.Context;


/**
 * dp,sp转换成px的工具类
 * Created by lgl on 16/3/23.
 */
public class DisplayUtils {

    /**
     * 将px值转换成dpi或者dp值,保持尺寸不变
     *
     * @param content
     * @param pxValus
     * @return
     */
    public static int px2dip(Context content, float pxValus) {
        final float scale = content.getResources().getDisplayMetrics().density;
        return (int) (pxValus / scale + 0.5f);
    }

    /**
     * 将dip和dp转化成px,保证尺寸大小不变。
     *
     * @param content
     * @param pxValus
     * @return
     */
    public static int dip2px(Context content, float pxValus) {
        final float scale = content.getResources().getDisplayMetrics().density;
        return (int) (pxValus / scale + 0.5f);
    }

    /**
     * 将px转化成sp,保证文字大小不变。
     *
     * @param content
     * @param pxValus
     * @return
     */
    public static int px2sp(Context content, float pxValus) {
        final float fontScale = content.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValus / fontScale + 0.5f);
    }

    /**
     * 将sp转化成px,保证文字大小不变。
     *
     * @param content
     * @param pxValus
     * @return
     */
    public static int sp2px(Context content, float pxValus) {
        final float fontScale = content.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValus / fontScale + 0.5f);
    }
}

其实的density就是前面所说的换算比例,这里使用的是公式换算方法进行转换,同时系统也提供了TypedValue帮助我们转换

    /**
     * dp2px
     * @param dp
     * @return
     */
    protected int dp2px(int dp){
        return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResources().getDisplayMetrics());
    }

    /**
     * sp2px
     * @param dp
     * @return
     */
    protected int sp2px(int sp){
        return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics());
    }

关于Android 屏幕适配,我目前在项目中实际用到过的有三种

  • 1,在项目res 目录下 建 dimens-ldpi,dimens-hdpi,dimens-xhdpi,dimens-xxhdpi 不同的文件夹 ,在各自里面都定义 dimens 文件, 然后再 根据 l:m:h:xh:xxh = 3:4:6:8:12 这个比例换算 ,比如 以hdpi 应该是12dp的话为参照的话, mdpi 要是为 8dp,xh为16dp,xxh 为24dp , 也就是在布局文件中起一个dimens的名称,在各个不同分辨率的文件中,创建相同的文件名的dimens ,但是他们的实际的值 就是上面换算的值,上面说的是布局的宽高大小,不是字体,其实字体也可以按照这种方式适配,字体写成dp 不写sp 可以避免用户主动调整系统字体,造成App 显示出来特别丑的可以这样做,我看微信好像就可以,你跳大字体,微信的字体也不变,但是你可以在微信的设置里设置微信的字体大小..这样写虽可以适配,但是有时候还是有误差, UI 在对还原度的时候再适当调整就好了,但就是计算太累的,麻烦,我记得好像有大神做了一个插件,可以一键生成.这个还好;
  • 2,中间一度使用UI 给的Hdpi 的数值,直接在布局文件中写上 多少dp 好像宽度,高度之类的也没有太大的问题,让我一度认为 第一种的适配的方式还有没有必要存在,
    -3, 目前使用的是 鸿洋大神的 Autolayout 号称屏幕适配的终结者,目前已不维护,但是使用方便,目前在实际使用中,并没有发现问题,推荐使用;
    使用的就是百分比 适配,根据不同的屏幕 动态设置 大小,开发者在布局文件中直接使用 px 就行了 ,简直不要太方便

这个px并不代表1像素,我在内部会进行百分比化处理,也就是说:720px高度的屏幕,你这里填写72px,占据10%;当这个布局文件运行在任何分辨率的手机上,这个72px都代表10%的高度,这就是本库适配的原理。

详细可以参考链接文章.

鸿洋大神的 AutoLayout 可以看 http://blog.csdn.net/lmj623565791/article/details/49990941

关于屏幕适配的文章 可以参考 https://www.jianshu.com/p/ec5a1a30694b

绘图的内容,可以查看 系列文章 https://www.jianshu.com/p/bd153dfc0095

SurfaceView

  • SurfaceView和View的区别

Android系统提供了VieW进行绘图处理, vieW可以满足大部分的绘图需求,但在某些时却也有些心有余而力不足,特别是在进行一些开发的时候。 我们知道,VieW通过刷新来视图, Android系统通过发出VSYNC信号来进行屏幕的重绘, 刷新的间隔时间为I6ms。在16ms内View完成了你所需要执行的所有操作,那么用户在视觉上, 就不会产生卡顿的感觉:而如果执行的操作逻辑太多,特别是需要频繁刷新的界面上,例如游戏界面,那么就塞主线程,从而导致画面卡顿,很多时候,在自定义VieW的Log中经常会看见如下示的警告

Skipped 47 frames! The application may be doing too much work on its main thread

这些警告的产生,很多情况下就是因为在绘制过程中, 处理逻辑太多造成的为了避免这一问题的产生一 Android系统提供了surfacevicW组件来解决这个问题,可以说是VieW的孪生兄弟,但它与View还是有所不同的,它们的区别主要体现

  • 1.View主要用于自动更新的情况下,而surfaceVicw主要适用于被动更新,例如频繁刷新
  • 2.View在主线程中刷新,而surfaceView通常会通过一 个子线程来进行页面刷新。
  • 3.View在绘制的时候没有双缓冲机制,而surfaceVicw在底层实现机制中就已经实现了双缓冲机制;

总结起来,如果你的自定义View需要频繁刷新,或者刷新时数据处理量比较大,那么你就可以考虑使用surfaceVicw取代View了

surfaceView的使用

  • 创建surfaceView

创建自定义的surfaceView,实现它的两个接口

public class SurfaView extends SurfaceView implements SurfaceHolder.Callback,Runnable

通过实现它的两个几口,就会有三个回调方法

  • 初始化SurfaceView
  • 使用SurfaceView

代码实现,Android 触摸画图 下面是View 的实现代码

package com.example.younger.youngertest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * Created by Younger on 2018/3/7.
 */
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private SurfaceHolder mHolder;

    private Canvas mCanvas;

    private boolean mIsDrawing;

    private int x=100;
    private int y=0;

    private Path path;
    private Paint paint;

    public MySurfaceView(Context context) {
        super(context);
        init();
    }

    public MySurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }


    private void init() {
        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
        paint = new Paint();
        path = new Path();
        paint.setColor(Color.parseColor("#112211"));
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(40);
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {

        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {

       long start = System.currentTimeMillis();

        while (mIsDrawing) {
            draw();
        }
        long end = System.currentTimeMillis();

        if (end-start<100){

            try {
                Thread.sleep(100-(end-start));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();

            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(path,paint);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mCanvas != null) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                path.moveTo(x, y);
                break;
            case MotionEvent.ACTION_MOVE:
                path.lineTo(x,y);
                break;
            case MotionEvent.ACTION_UP:
                break;

        }
        return true;
    }
}

好了,关于Android 绘图只知识, 和屏幕适配基本就先写到这,如果以后有新的内容,会继续补充

你可能感兴趣的:(第六章 Android 绘图机制与屏幕适配)