Android常用知识点-[常用备忘,持续更新]

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011200604/article/details/83657201

目前包含:

大纲:

[自定义view参数]

[shape/layer-list/selector]

[view与activity生命]

[view事件分发与滑动冲突]

[ScrollBy与ScrollTo]

[scaleType]

[Android版本号] 

[drawable -hdpi]



Android自定义View——自定义样式整理-步骤

例: 可以设置宽高比例的ImageView   GitHub:https://github.com/1993hzw/Androids

①首先在res/values/目录下创建attrs.xml(文件名可自定义),内容如下:(示例)

<!-- res/values/attrs.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="ShapeImageView">
    <attr name="shape" format="enum">
      <enum name="rect" value="1"/>
      <enum name="circle" value="2"/>
    </attr>
    <attr name="round_radius" format="dimension"/>
    <attr name="border_size" format="dimension"/>
    <attr name="border_color" format="color"/>
  </declare-styleable>
</resources>
declare-styleable标签的format属性的值可以为:
reference  参考某一资源id
color  颜色值,#000000
boolean  布尔值,true
dimension  尺寸值,12dp
float  浮点数,0.1
integer  整型,12
string  字符串,abc
fraction  百分数,50%
enum  枚举值
flag  位或运算
format可以指定多种类型,如<attr name = "background" format = "reference|color"/>,
这样background可以指定某一图片资源id,也可以直接设置颜色。

写好样式后,接下来就要在代码中获取这些样式的值,并进行设置:(示例)

public class ShapeImageView extends ImageView {
    public static int SHAPE_REC = 1; // 矩形
    public static int SHAPE_CIRCLE = 2; // 圆形
    private float mBorderSize = 0; // 边框大小,默认为0,即无边框
    private int mBorderColor = Color.WHITE; // 边框颜色,默认为白色
    private int mShape = SHAPE_REC; // 形状,默认为直接矩形
    private float mRoundRadius = 0; // 矩形的圆角半径,默认为0,即直角矩形
    public ShapeImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public ShapeImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }
// 根据xml布局文件设置相关属性
    private void init(AttributeSet attrs) {
        TypedArray a = getContext().obtainStyledAttributes(attrs,
                R.styleable.ShapeImageView);
        mShape = a.getInt(R.styleable.ShapeImageView_shape, mShape);
        mRoundRadius = a.getDimension(R.styleable.ShapeImageView_round_radius, mRoundRadius);
        mBorderSize = a.getDimension(R.styleable.ShapeImageView_border_size, mBorderSize);
        mBorderColor = a.getColor(R.styleable.ShapeImageView_border_color, mBorderColor);
        a.recycle();
    }
}

在布局文件中应用自定义样式有两种方式:

xmlns:app="http://schemas.android.com/apk/res/com.example.androidsdemo" 最后一个 / 后面跟着的是应用包名

xmlns:app="http://schemas.android.com/apk/res-auto" 自动引入自定义样式

如果你的项目是作为库工程给他人引用,建议采用第2钟方式。app是自定义样式的别名,可自定义。引入自定义样式后,通过 别名:属性名="value" 的形式设置属性,如app:shape="rect"。


Android样式开发之shape:

Android XML shape 标签使用详解:

标准示例:

<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="#7F000000"/>
<corners
android:radius="4dp"/>
</shape>

shape 标签定义的 Drawable 类型对应 GradientDrawable

Shape支持的类型形状

    rectangle: 矩形,默认的形状,可以画出直角矩形、圆角矩形、弧形等

  oval: 椭圆形,用得比较多的是画正圆

  line: 线形,可以画实线和虚线

  ring: 环形,可以画环形进度条

基本属性(corners、gradient、padding、size、solid、stroke)

rectangle

  这种类型应该是我们使用的最多的类型了,一些控件的背景、布局的背景都可以使用它来完成。

  我们来看详细的介绍:

*  solid: 设置形状填充的颜色,只有android:color一个属性

    * android:color 填充的颜色

* padding: 设置内容与形状边界的内间距,可分别设置左右上下的距离

    * android:left 左内间距

    * android:right 右内间距

    * android:top 上内间距

    * android:bottom 下内间距

* gradient: 设置形状的渐变颜色,可以是线性渐变、辐射渐变、扫描性渐变

    * android:type 渐变的类型

        * linear 线性渐变,默认的渐变类型

        * radial 放射渐变,设置该项时,android:gradientRadius也必须设置

        * sweep 扫描性渐变

    * android:startColor 渐变开始的颜色

    * android:endColor 渐变结束的颜色

    * android:centerColor 渐变中间的颜色

    * android:angle 渐变的角度,线性渐变时才有效,必须是45的倍数,0表示从左到右,90表示从下到上

    * android:centerX 渐变中心的相对X坐标,放射渐变时才有效,在0.0到1.0之间,默认为0.5,表示在正中间

    * android:centerY 渐变中心的相对X坐标,放射渐变时才有效,在0.0到1.0之间,默认为0.5,表示在正中间

    * android:gradientRadius 渐变的半径,只有渐变类型为radial时才使用

    * android:useLevel 如果为true,则可在LevelListDrawable中使用

* corners: 设置圆角,只适用于rectangle类型,可分别设置四个角不同半径的圆角,当设置的圆角半径很大时,比如200dp,就可变成弧形边了

    * android:radius 圆角半径,会被下面每个特定的圆角属性重写

    * android:topLeftRadius 左上角的半径

    * android:topRightRadius 右上角的半径

    * android:bottomLeftRadius 左下角的半径

    * android:bottomRightRadius 右下角的半径

* stroke: 设置描边,可描成实线或虚线。

    * android:color 描边的颜色

    * android:width 描边的宽度

    * android:dashWidth 设置虚线时的横线长度

    * android:dashGap 设置虚线时的横线之间的距离

oval

  oval用来画椭圆,而在实际应用中,更多是画正圆,比如消息提示,圆形按钮等,下图是一些例子:

  size: 设置形状默认的大小,可设置宽度和高度

    * android:width 宽度

    * android:height 高度

ring

   首先,shape根元素有些属性只适用于ring类型,先过目下这些属性吧:

* android:innerRadius  内环的半径

* android:innerRadiusRatio  浮点型,以环的宽度比率来表示内环的半径,默认为3,表示内环半径为环的宽度除以3,该值会被android:innerRadius覆盖

* android:thickness  环的厚度

* android:thicknessRatio  浮点型,以环的宽度比率来表示环的厚度,默认为9,表示环的厚度为环的宽度除以9,该值会被android:thickness覆盖

* android:useLevel  一般为false,否则可能环形无法显示,只有作为LevelListDrawable使用时才设为true

layer-list的基本使用介绍

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:left="3dp"
          android:top="6dp">
        <shape>
            <solid android:color="#b4b5b6"/>
        </shape>
    </item>
    <item android:bottom="6dp"
          android:right="3dp">
        <shape>
            <solid android:color="#fff"/>
        </shape>
    </item>
</layer-list>

android selector详解

常用的属性分析
android:state_accessibility_focused是否能够获取焦点
android:state_selected是否选中
android:state_focused是否获得焦点
android:state_pressed是否点击
android:state_enabled设置是否响应事件,指所有事件
android:state_checkable是否可能选中
android:state_checked否是选中
android:state_active是否活动
android:state_activated
android:state_window_focused

//示例:

?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 当前窗口失去焦点时 -->
<item android:drawable="@drawable/bg_btn_lost_window_focused" android:state_window_focused="false" />
<!-- 不可用时 -->
<item android:drawable="@drawable/bg_btn_disable" android:state_enabled="false" />
<!-- 按压时 -->
<item android:drawable="@drawable/bg_btn_pressed" android:state_pressed="true" />
<!-- 被选中时 -->
<item android:drawable="@drawable/bg_btn_selected" android:state_selected="true" />
<!-- 被激活时 -->
<item android:drawable="@drawable/bg_btn_activated" android:state_activated="true" />
<!-- 默认时 -->
<item android:drawable="@drawable/bg_btn_normal" />
</selector>

有几点还是需要注意和了解的

1. selector作为drawable资源时,item指定android:drawable属性,并放于drawable目录下;

2. selector作为color资源时,item指定android:color属性,并放于color目录下;

3. color资源也可以放于drawable目录,引用时则用@drawable来引用,但不推荐这么做,drawable资源和color资源最好还是分开;

4. android:drawable属性除了引用@drawable资源,也可以引用@color颜色值;但android:color只能引用@color;

5. item是从上往下匹配的,如果匹配到一个item那它就将采用这个item,而不是采用最佳匹配的规则;所以设置默认的状态,一定要写在最后,如果写在前面,则后面所有的item都不会起作用了。

另外,selector标签下有两个比较有用的属性要说一下,添加了下面两个属性之后,则会在状态改变时出现淡入淡出效果,但必须在API Level 11及以上才支持:

* android:enterFadeDuration 状态改变时,新状态展示时的淡入时间,以毫秒为单位

* android:exitFadeDuration 状态改变时,旧状态消失时的淡出时间,以毫秒为单位


View的关键生命周期与Activity生命周期关系

Activity --> onCreate()
View     --> 构造View()
View     --> onFinishInflate()
Activity --> onStart()
Activity --> onResum()
View     --> onAttachedToWindow()
View     --> onMeasure()
View     --> onSizeChanged()
View     --> onLayout()
View     --> onDraw()
View     --> onWindowFocusChanged()  true
Activity --> onPause()
View     --> onWindowFocusChanged()  false
Activity --> onStop()
Activity --> onDestroy()
View     --> onDetackedFromWindow()

view事件分发与滑动冲突

滑动冲突的解决方式:

外部拦截法:

1.父元素重写onInterceptTouchEvent()

内部拦截法

1. 重写子元素dispatchTouchEvent()

2.父元素重写onInterceptTouchEvent()ACTION_DOWN return false;

    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        boolean intercepted = false;

        int x = (int) ev.getX();

        int y = (int) ev.getY();

        switch (ev.getAction()){

            case MotionEvent.ACTION_DOWN:

                intercepted = false;

                break;

            case MotionEvent.ACTION_MOVE:

                if("父容器的点击事件"){

                    intercepted = true;

                }else {

                    intercepted = false;

                }

                break;

            case MotionEvent.ACTION_UP:

                intercepted = false;

                break;

        }

        mLastXIntercept = x;

        mLastYIntercept = x;

        return intercepted;

    }

    @Override

    public boolean dispatchTouchEvent(MotionEvent event) {

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

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

        switch (event.getAction()){

            case MotionEvent.ACTION_DOWN:

                getParent().requestDisallowInterceptTouchEvent(true);

                break;

            case MotionEvent.ACTION_MOVE:

                int deltaX =  x - mLastX;

                int deltaY =  x - mLastY;

                if("父容器的点击事件"){

                    getParent().requestDisallowInterceptTouchEvent(false);

                }

                break;

            case MotionEvent.ACTION_UP:

               break;

        }

        mLastX = x;

        mLastY = y;

        return super.dispatchTouchEvent(event);

    }

---------------------父容器

   @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        int action = ev.getAction();

        if(action == MotionEvent.ACTION_DOWN){

            return false;

        }else {

            return true;

        }

    }

所谓的外部拦截费是指点击事件都先经过父容器的拦截处理,如果父容器需要这个事件就给给他,这里我们还得重写我们的onIterceptTouchEvent

述代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件这个条件即可,其他均不需做修改并且也不能修改。这里对上述代码再描述一下,在onInterceptTouchEvent方法中,首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP事件都会直接交由父容器处理,这个时候事件没法再传递给子元素了;其次是ACTION_MOVE事件,这个事件可以根据需要来决定是否拦截,如果父容器需要拦截就返回true,否则返回false;最后是ACTION_UP事件,这里必须要返回false,因为ACTION_UP事件本身没有太多意义考虑一种情况,假设事件交由子元素处理,如果父容器在ACTION_UP时返回了true,会导致子元素无法接收到ACTION_UP事件,这个时候子元素中的onClick事件就无法触发,但是父容器比较特殊,一旦它开始拦截任何一个事件,那么后续的事件都会交给它处理,而ACTION_UP作为最后一个事件也必定可以传递给父容器,即便父容器的onInterceptTouchEvent方法在ACTION_UP时返回了false。

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素要消耗此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,

除了子元素需要处理之外,父元素默认也要拦截除ACTION_DOWN之外的其他事件,这样当子元素调用getParent().requestDisallowInterceptTouchEvent(true)方法时,父元素才能继续拦截所需要的事件

为什么父容器不能拦截ACTION_DOWN事件呢?那是因为ACTION_DOWN事件并接受FLAG_DISALLOW_DOWN这个标记位的控制,所以一旦父容器拦截,那么所有的事件都无法传递到子元素中,这样额你不拦截就无法起作用了

事件的分发:

事件的传递规则 伪代码  : 
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if(onInterceptTouchEvent(ev)){
            consume = onTouchEvent(ev);
        }else {
            consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }

上面代码主要涉及到以下三个方法:

上面代码主要涉及到以下三个方法:

public boolean dispatchTouchEvent(MotionEvent ev);
这个方法用来进行事件的分发。如果事件传递给当前view,则调用此方法。返回结果表示是否消耗此事件,受onTouchEvent和下级View的dispatchTouchEvent方法影响。

public boolean onInterceptTouchEvent(MotionEvent ev);
这个方法用来判断是否拦截事件。在dispatchTouchEvent方法中调用。返回结果表示是否拦截。
public boolean onTouchEvent(MotionEvent ev);
这个方法用来处理点击事件。在dispatchTouchEvent方法中调用,返回结果表示是否消耗事件。如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

我们可以大致的了解传递的规则就是,对于一个根ViewGroup来说,点击事件产生以后,首先传递给

产,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onIntereptTouchEvent方法返回true就表示它要控截当前事件,接着事件就会交给这个ViewGroup处理,则他的onTouchEvent方法就会被调用;如果这个ViewGroup的onIntereptTouchEvent方法返回false就表示不需要拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的onIntereptTouchEvent方法就会被调用,如此反复直到事件被最终处理。

当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTooch方法会被回调。这时事件如何处理还要看onTouch的返回值,如果返回false,那当前的View的方法OnTouchListener会被调用;如果返回true,那么onTouchEvent方法将不会被调用。由此可见,给View设置的OnTouchListener,其优先级比onTouchEvent要高,在onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会用。可以看出,平时我们常用的OnClickListener,其优先级最低,即处于事尾端。 由此可见处理事件时的优先级关系: onTouchListener > onTouchEvent >onClickListener

当一个点击事件产生后,它的传递过程遵循如下顺序:Activity>Window-View,即事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给顶级View顶级View接收到事件后,就会按照事件分发机制去分发事件。考虑一种情况,如果一个view的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,依此类推,如果所有的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用。

关于事件传递的机制,这里给出一些结论,根据这些结论可以更好地理解整个传递机制,如下所示。

(1)同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏慕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最后以up结束

(2)正常情况下,一个事件序列只能被一个Visw拦截且消耗。这一条的原因可以参考(3),因为一旦一个元素拦截了某此事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个Vew将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。

(3)某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceprTouchEvent不会再被调用。这条也很好理解,就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他方法都直接交给它来处理,因此就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。

(4)某个View一旦开始处理事件,如果它不消耗ACTON_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了,这就好比上级交给程序员一件事,如果这件事没有处理好,短期内上级就不敢再把事情交给这个程序员做了,二者是类似的道理。

(5)如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。

(6)ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false

(7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。

(8)view的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false),View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView 的clickable属性默认为false

(9)view 的enable.属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longclickable有一个为true,那么它的onTouchEvent就返会true。

(10)onclick会发生的前提实际当前的View是可点击的,并且他收到了down和up的事件

(11)事件传递过程是由外到内的,理解就是事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterptTouchEvent方法可以再子元素中干预元素的事件分发过程,但是ACTION_DOWN除外


View的ScrollBy与ScrollTo

调用View的scrollTo()和scrollBy()是用于滑动View中的内容,而不是把某个View的位置进行改变

如果想改变莫个View在屏幕中的位置,可以使用 offsetLeftAndRight(int offset)  /  offsetTopAndBottom(int offset)方法。 button.offsetLeftAndRignt(300) 表示将button控件向左移动300个像素。

scrollTo(int x, int y) 是将View中内容滑动到相应的位置,参考的坐标系原点为parent View的左上角。(视觉效果与传参正负相反)

Android View绝对相对坐标系

MotionEvent坐标方法

解释

getX()

当前触摸事件距离当前View左边的距离

getY()

当前触摸事件距离当前View顶边的距离

getRawX()

当前触摸事件距离整个屏幕左边的距离

getRawY()

当前触摸事件距离整个屏幕顶边的距离

Android View滑动相关坐标系

关于View提供的与坐标息息相关的另一组常用的重要方法就是滚动或者滑动相关的,下面我们给出相关的解释
(特别注意:View的scrollTo()和scrollBy()是用于滑动View中的内容,而不是改变View的位置;改变View在屏幕中的位置可以使用offsetLeftAndRight()和offsetTopAndBottom()方法,他会导致getLeft()等值改变。),如下:

offsetLeftAndRight(int offset)    水平方向挪动View,offset为正则x轴正向移动,移动的是整个View,getLeft()会变的,自定义View很有用。
offsetTopAndBottom(int offset)    垂直方向挪动View,offset为正则y轴正向移动,移动的是整个View,getTop()会变的,自定义View很有用。
scrollTo(int x, int y)    将View中内容(不是整个View)滑动到相应的位置,参考坐标原点为ParentView左上角,x,y为正则向xy轴反方向移动,反之同理。
scrollBy(int x, int y)    在scrollTo()的基础上继续滑动xy。
setScrollX(int value)    实质为scrollTo(),只是只改变Y轴滑动。
setScrollY(int value)    实质为scrollTo(),只是只改变X轴滑动。
getScrollX()/getScrollY()    获取当前滑动位置偏移量。

图片缩放模式scaleType

Glide-图片的剪裁(ScaleType)

(1)matrix
不缩放 ,图片与控件 左上角 对齐,当图片大小超过控件时将被 裁剪

(2)center
不缩放 ,图片与控件 中心点 对齐,当图片大小超过控件时将被 裁剪

(3)centerInside
以完整显示图片为目标, 不剪裁 ,当显示不下的时候将缩放,能够显示的情况下不缩放**

(4)centerCrop
以填满整个控件为目标,等比缩放,超过控件时将被 裁剪 ( 宽高都要填满 ,所以只要图片宽高比与控件宽高比不同时,一定会被剪裁)

(5)fitCenter(默认)
自适应控件, 不剪裁 ,在不超过控件的前提下,等比 缩放 到 最大 ,居中显示

(6)fitStart
自适应控件, 不剪裁 ,在不超过控件的前提下,等比 缩放 到 最大 ,靠左(上)显示

(7)fitEnd
自适应控件, 不剪裁 ,在不超过控件的前提下,等比 缩放 到 最大 ,靠右(下)显示

(8)fitXY
以填满整个控件为目标, 不按比例 拉伸或缩放(可能会变形), 不剪裁

Android版本号和API Level对应关系


Android视频之res里面的drawable(ldpi、mdpi、hdpi、xhdpi、xxhdpi)

drawable

适用

常规: 360dp * 640dp

DP

比例值 比例系数

dp与px计算图

ldpi:低密度屏幕;约为 120dpi。

mdpi:中等密度(传统 HVGA)屏幕;约为 160dpi。

hdpi:高密度屏幕;约为 240dpi。

xhdpi:超高密度屏幕;约为 320dpi。API 级别 8 中新增配置

xxhdpi:超超高密度屏幕;约为 480dpi。API 级别 16 中新增配置

xxxhdpi:超超超高密度屏幕使用(仅限启动器图标,请参阅“支持多个屏幕”中的注释);约为 640dpi。 API 级别 18 中新增配置

QVGA (240×320)

HVGA (320×480) 

WVGA (480×800),(480×854)

720P(1280*720) 

1080p(1920*1080 )

4K(3840×2160)

320dp

320dp

320dp

360dp

360dp

540dp

3

4

6

8

12

16

.075

1.0

1.5

2.0

3.0

4.0

ldpi:1dp=0.75px

mdpi:1dp=1px

hdpi:1dp=1.5px

xhdpi:1dp=2px

xxhdpi:1dp=3px

xxxhdpi:1dp=4px

六个主要密度之间的缩放比为 3:4:6:8:12:16(忽略 tvdpi 密度)。因此,9x9 (ldpi) 位图相当于 12x12 (mdpi)、18x18 (hdpi)、24x24 (xhdpi) 位图,依此类推。

Android studio mipmap文件夹只存放启动图标icon(Drawablex下会进行资源优化,删除不用的密度文件夹)

dpi
每英寸点数,全称dots per inch。用来表示屏幕密度,即屏幕物理区域中的像素量。高密度屏幕比低密度屏幕在给定物理区域的像素要多。
dp
即dip,全称device independent pixel。设备独立像素,是一种虚拟像素单位,用于以密度无关方式表示布局维度或位置,以确保在不同密度的屏幕上正常显示UI。在160dpi的设备上,1dp=1px。
density
设备的逻辑密度,是dip的缩放因子。以160dpi的屏幕为基线,density=dpi/160。

Drawable查找流程

Android系统会依据特定的原则来查找各drawable目录下的图片。查找流程为:
1. 先查找和屏幕密度最匹配的文件夹。
2. 如果在最匹配的目录没有找到对应图片,就会向更高密度的目录查找,直到没有更高密度的目录。
3. 如果一直往高密度目录均没有查找,Android就会查找drawable-nodpi目录。
4. 如果在drawable-nodpi目录也没有查找到,系统就会向比最匹配目录密度低的目录依次查找,直到没有更低密度的目录。
假如当前设备的dpi是320,系统会优先去drawable-xhdpi目录查找,如果找不到,会依次查找xxhdpi 
→ xxxhdpi → hdpi → mdpi → ldpi。对于不存在的drawable-[density]目录直接跳过,中间任一目录查找到资源,则停止本次查找。

drawable-nodpi文件夹是在匹配密度文件夹和更高密度文件夹都找不到的情况下才会去这里查找图片的
Google: 根据屏幕尺寸限定符选择资源时,如果没有更好的匹配资源,
则系统将使用专为小于当前屏幕的屏幕而设计的资源(例如,如有必要,大尺寸屏幕将使用标准尺寸的屏幕资源)。
 但是,如果唯一可用的资源大于当前屏幕,则系统不会使用这些资源,并且如果没有其他资源与设备配置匹配,应用将会崩溃

不同drawble目录图片的放大和缩小 

前述说到Android为了能够更好地适配各种屏幕,会依据当前设备的dpi对drawable-[density]
目录中的图片进行缩放,那么什么情况下图片被放大,什么情况下图片被缩小呢?
为了更好的描述,把“符合当前设备dpi的drawable目录”表示为”匹配目录“。
比如,设备的dpi为320,这匹配目录为drawable-xhdpi;设备的dpi为150,则匹配目录为drawable-mdpi。图片的放大和缩小遵循以下规律:

如果图片所在目录为匹配目录,则图片会根据设备dpi做适当的缩放调整。
如果图片所在目录dpi低于匹配目录,那么该图片被认为是为低密度设备需要的,
   现在要显示在高密度设备上,图片会被放大。(比如图应该是xxhdpi的(3x),放在了mdpi(1x),所在目录低于应 
   该的目录)
如果图片所在目录dpi高于匹配目录,那么该图片被认为是为高密度设备需要的,
   现在要显示在低密度设备上,图片会被缩小。(比如图应该是mdpi的(1x),放在了xxhdpi(3x),所在目录比应 
   在的目录高)
如果图片所在目录为drawable-nodpi,则无论设备dpi为多少,保留原图片大小,不进行缩放。
实际缩放系数为: 设备dpi / 图片所在drawable目录对应的最大dpi

实际缩放系数为: 设备dpi / 图片所在drawable目录对应的最大dpi

玩转Android drawable图片适配

猜你喜欢

转载自blog.csdn.net/u011200604/article/details/83657201