1. 程式人生 > >Android常用知識點-[常用備忘,持續更新]

Android常用知識點-[常用備忘,持續更新]

目前包含:

大綱:

[自定義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圖片適配