// 获取屏幕宽高(方法1)  
 int screenWidth = getWindowManager().getDefaultDisplay().getWidth(); // 屏幕宽(像素,如:480px)  
 int screenHeight = getWindowManager().getDefaultDisplay().getHeight(); // 屏幕高(像素,如:800p)  

 // 获取屏幕密度(方法2)  
 DisplayMetrics dm = new DisplayMetrics();  
 dm = getResources().getDisplayMetrics();  
 float density = dm.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0)  
 int densityDPI = dm.densityDpi; // 屏幕密度(每寸像素:120/160/240/320)  
 float xdpi = dm.xdpi;  
 float ydpi = dm.ydpi;  

 screenWidth = dm.widthPixels; // 屏幕宽(像素,如:480px)  
 screenHeight = dm.heightPixels; // 屏幕高(像素,如:800px)  

 // 获取屏幕密度(方法3)  
 dm = new DisplayMetrics();  
 getWindowManager().getDefaultDisplay().getMetrics(dm);  
 density = dm.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0)  
 densityDPI = dm.densityDpi; // 屏幕密度(每寸像素:120/160/240/320)  
 xdpi = dm.xdpi;  
 ydpi = dm.ydpi;  
  
 int screenWidthDip = dm.widthPixels; // 屏幕宽(dip,如:320dip)  
 int screenHeightDip = dm.heightPixels; // 屏幕宽(dip,如:533dip)  

 screenWidth = (int)(dm.widthPixels * density + 0.5f); // 屏幕宽(px,如:480px)  
 screenHeight = (int)(dm.heightPixels * density + 0.5f); // 屏幕高(px,如:800px)



内容区域
android中的内容区域,实际上是一块系统默认的名为android.id.content的单帧布局,setContentView()方法相当于添加View到这个单帧布局中,并覆盖。

2、获取屏幕宽高

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DisplayMetrics dm = new DisplayMetrics();
        // 获取屏幕信息
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int screenWidth = dm.widthPixels;
        int screenHeigh = dm.heightPixels;
        Log.v("获取屏幕宽度", "宽度:" + screenWidth + ",高度:" + screenHeigh);
    }



二、获取控件宽高


控件View有getHeight()和getwidth()方法可以获取宽高,但是如果直接在onCreate()方法中获取控件宽高,获取到的值是0,至于原因的是因为onCreate()方法中只是提供了数据初始化此时还没有正式绘制图形。而绘制图形在OnDraw中进行,此时计算又显得太晚。容易想到的办法是:希望能在程序刚刚测量好某个指定控件后,拿到它的宽度和高度立刻进行计算或数据初始化。这就需要有一个方法来监听到这个事件的发生,幸好Android提供了这样的机制,利用View类中的getViewTreeObserver方法,可以获取到指定View的观察者,在绘制控件前的一刹那进行回调,这样速度上又不耽误,得到的数据由是准确的。

方法一:

public class MainActivity extends AppCompatActivity {
     TextView firstTxt;
  
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
  
         firstTxt = (TextView) findViewById(R.id.hello_word_txt);
         ViewTreeObserver viewTreeObserver = firstTxt.getViewTreeObserver();
         viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
                 int height = firstTxt.getHeight();
                 int width = firstTxt.getWidth();
                 Log.v("获取TextView宽高", "宽度:" + width + ",高度:" + height);
                 return true;
             }
         });
  
     }
 }




方法二:


关于方法二,下面有一段我自己的简单理解


将一个runnable添加到Layout队列中:View.post()
简单地说,只要用View.post()一个runnable就可以了。runnable对象中的方法会在View的measure、layout等事件后触发UI事件队列会按顺序处理事件。在setContentView()被调用后,事件队列中会包含一个要求重新layout的message,所以任何你post到队列中的东西都会在Layout发生变化后执行。
 

firstTxt.post(new Runnable() {
            @Override
            public void run() {
                firstTxt.getHeight(); //height is ready
                firstTxt.getWidth();
            }
        }


关于方法二的一些理解

上面提到过绘制控件是在ondraw()方法中进行,在ondraw中获取控件宽高则太晚,而在onMeasure()中测量又太早。那我们尝试在onLayout()中获取控件宽高是否可行。 onLayout()和ondraw()都是控件View生命周期中的回调方法。至于View的生命周期,我也还没接触过,等后面深入学习了再总结一下。

首先新建一个类继承自TextView,然后重写里面的onLayout()和onMeasure()方法

public class MyTextView extends TextView {
  
  public MyTextView(Context context) {
         super(context);
     }
  
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
  
     public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
  
     public MyTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
  
     
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         //获取MyTextView当前实例的高
         int height = this.getHeight();
         //获取MyTextView当前实例的宽
         int width = this.getWidth();
          
         //通过Log.v打印输出显示
         Log.v("onMeasure获取控件宽高", "高:" + height + ",宽:" + width);
     }
  
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         //获取MyTextView当前实例的高
         int height = this.getHeight();
         //获取MyTextView当前实例的宽
         int width = this.getWidth();
  
         //通过Log.v打印输出显示
         Log.v("onLayout获取控件宽高", "高:" + height + ",宽:" + width);
     }
      
 }



再在程序入口的Acitvity中的onCreate()方法中实例化自定义的MyTextView


public class MainActivity extends AppCompatActivity {
     MyTextView mMyTextView;
  
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         mMyTextView = (MyTextView) findViewById(R.id.mytextview_txt);
     }
 }



从上面可以看出MyTextView第一次回调onMeasure()方法时,还没有获取到控件的宽高数据。上面提到过"如果直接在onCreate()方法中获取控件宽高,获取到的值是0",那我们可以假设一下activity的onCreate()方法执行时在View执行第一次onMeasure()方法之前,我们尝试通过以下方法看能否验证这一假设

首先在上面新建的MyTextView中添加两个我们自定义的方法,一个是setMyTextViewSize(),另外一个是getMyTextViewSize(),代码如下:

public class MyTextView extends TextView {
     int mMyTextViewHeight;
     int mMyTextViewWidth;
  
     public MyTextView(Context context) {
         super(context);
     }
  
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
  
     public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
  
     public MyTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
  
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         //获取MyTextView当前实例的高
         int height = this.getHeight();
         //获取MyTextView当前实例的宽
         int width = this.getWidth();
  
         //调用setMyTextViewSize,使其他类可以通过getMyTextViewSize()可以获取到
         //mMyTextViewHeight和mMyTextViewWidth变量的值,即高和宽
         setMyTextViewSize(height, width);
          
         //通过Log.v打印输出显示
         Log.v("onMeasure获取控件宽高", "高:" + height + ",宽:" + width);
     }
  
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         //获取MyTextView当前实例的高
         int height = this.getHeight();
         //获取MyTextView当前实例的宽
         int width = this.getWidth();
         //与上同理
         setMyTextViewSize(height, width);
          
         //通过Log.v打印输出显示
         Log.v("onLayout获取控件宽高", "高:" + height + ",宽:" + width);
     }
  
     public void setMyTextViewSize(int height, int width) {
         mMyTextViewHeight = height;
         mMyTextViewWidth = width;
     }
  
     public int[] getMyTextViewSize() {
         int[] viewSize = new int[]{mMyTextViewHeight, mMyTextViewWidth};
         return viewSize;
  
     }
 }



再在程序入口的Acitvity的onCreate()方法中上面已经实例化得到的MyTextView的getMyTextViewsize()方法尝试获取控件宽高

public class MainActivity extends AppCompatActivity {
     MyTextView mMyTextView;
  
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         mMyTextView = (MyTextView) findViewById(R.id.mytextview_txt);
         int[] myTextViewSize = mMyTextView.getMyTextViewSize();
         Log.v("入口acitivity的onCreate获取控件宽高", "高:" + myTextViewSize[0] + ",宽:" + myTextViewSize[1]);
     }
 }



通过上图可以发现Acitivity的onCreate()方法执行时机是在View的第一次回调onMeasure()之前,View第一次回调onMeasure()方法的时候还没有获取到宽高,自然Acitivity的onCreate()方法中也无法获取到控件宽高。那么,通过上面的打印信息,我们可以设想如果在onLayout()方法已经获取到View的宽高时,Activity的onCreate()再获取宽高就行了。我想到的一种思路是在Acitivity的onCreate()方法循环调用MyTextView的onLayout()方法,但是我们直到在android的主线程也就是UI线程中执行死循环会导致页面无法显示,那么我们应该在onCreate()方法中开辟一条子线程 ,方法循环调用MyTextView的onLayout()方法,直到获取到宽高。要实现这个思路的话,我们可以用到Handler。

下面是具体的代码实现:

public class MainActivity extends AppCompatActivity {
     MyTextView mMyTextView;
     //使用Handler默认的无参构造方法时,Handler执行的线程即声明Handler的线程,在这里是在主线程,也就是UI线程中
     Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             Log.v("Handler获取控件宽高", "高:" + msg.arg1 + ",宽:" + msg.arg2);
         }
     };
  
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         mMyTextView = (MyTextView) findViewById(R.id.mytextview_txt);
         new Thread(new Runnable() {
             @Override
             public void run() {
                 int[] viewSize = mMyTextView.getMyTextViewSize();
                 Log.v("onCreate()获取控件宽高", "高:" + viewSize[0] + ",宽:" + viewSize[1]);
                 while (viewSize[0] == 0) {
                     viewSize = mMyTextView.getMyTextViewSize();
                     if (viewSize[0] != 0) {
                         Message msg = Message.obtain();
                         msg.arg1 = viewSize[0];
                         msg.arg2 = viewSize[1];
                         mHandler.sendMessage(msg);
                     }
                 }
             }
         }).start();
     }
 }



通过上面的方法我们就可以在onCreate()方法中获取View的宽高了