本文参考http://blog.csdn.net/guolin_blog/article/details/12921889系列文章。
一、View初识
Android的开发涉及到UI开发,数据的存储,网络的处理等等,所需数据有后台接口提供,因此也有人把Android看成是前端开发!当然这里还是区别于Web前端开发的,Web前端有各种各样的框架,原生的JS用的越来越少,前端可以很轻松的实现一个炫酷的效果,作为Android来看我们也是同类,怎样才能让界面变得好看。
(1)、一切从setContentView()说起
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
Window相当于是一个容器成放着View,
setContentView()内部其实调用了LayoutInflater,来加载布局,那么LayoutInflater又是如何使用的呢?
LayoutInflater layoutInflater = LayoutInflater.from(context);
这个可能在activity中不明显,回想我们写的Adapter,加载布局的时候,是不是用到了它!
我们来activity里面实现一下
public class MainActivity extends AppCompatActivity {
private ConstraintLayout mConstraintLayout;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
mConstraintLayout = findViewById(R.id.con_lay);
LayoutInflater mLayoutInflater = LayoutInflater.from(mContext);
View mButtonLayout = mLayoutInflater.inflate(R.layout.butoon, null);
mConstraintLayout.addView(mButtonLayout);
}
}
button的布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</Button>
通过这样的方式就把按钮加载了我们的布局上。当然这里我们还有一个问题,就是我们发现这种方式我们不能改变button的大小。
如果我们给button在套一个布局的话,发现就可以了,如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</Button>
</LinearLayout>
那么我们会有这样一个问题,我们平时写的布局如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/con_lay"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
是可以指定大小的,那为啥之前的button布局就不可以,难道系统也为我们在外层又自动加了一层布局?
验证一下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
mConstraintLayout = findViewById(R.id.con_lay);
LayoutInflater mLayoutInflater = LayoutInflater.from(mContext);
View mButtonLayout = mLayoutInflater.inflate(R.layout.butoon, null);
mConstraintLayout.addView(mButtonLayout);
Log.e(TAG, "onCreate: "+ mConstraintLayout.getParent() );
}
日志:
2019-08-24 18:43:48.931 15123-15123/? E/MainActivity: onCreate: androidx.appcompat.widget.ContentFrameLayout{af2b984 V.E...... ......I. 0,0-0,0 #1020002 android:id/content}
其实Android界面实现是很复杂的,一个界面大概是由两部分组成 标题跟内容,这也是为啥setContentView()。
二、View的绘制流程
任何一个视图都不必凭空出现在屏幕上,都会经过三个过程,即onMeasure()、onLayout()和onDraw(),
(1)、onMeasure()
measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:
1)、EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2)、AT_MOST
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3)、UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(400,400);
}
}
需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
(2)、onLayout()
measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。正如其名字所描述的一样,这个方法是用于给视图进行布局的。
我们看下View的onLayout()方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
throw new RuntimeException("Stub!");
}
看下ViewGroup的onLayout()方法
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
//看下父布局里的方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() > 0) {
View childView = getChildAt(0);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() > 0) {
View childView = getChildAt(0);
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
}
在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。说到这里,我相信很多朋友长久以来都会有一个疑问,getWidth()方法和getMeasureWidth()方法到底有什么区别呢?它们的值好像永远都是相同的。其实它们的值之所以会相同基本都是因为布局设计者的编码习惯非常好,实际上它们之间的差别还是挺大的。
首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
(3)、onDraw()
protected void onDraw(Canvas canvas) {
throw new RuntimeException("Stub!");
}
Draw的绘制过程遵循如下几步:
绘制背景background.draw(canvas)
绘制自己,(onDraw)
绘制子视图,(dispatchDraw)
绘制装饰(onDrawScrollBars)
三、自定义View
自定义View,涉及到View的层次结构,事件的分发机制,View的工作原理等细节,是一个比较综合的知识点。
自定义View的一般分类:
继承View重写onDraw方法
继承ViewGroup派生特殊的Layout
继承特定的View(Button 之类的)
继承特定的ViewGroup(LinearLayout之类的)
观看实际的自定义View的Demo
四、自定义View注意事项
(1)、让View支持wrap_content
直接继承View和ViewGroup的控件,不在onMeasure中对wrap_content做处理的话,外界不能正常使用。
(2)、让View支持padding
直接继承自View的控件,如果不在draw方法中处理padding,那么padding属性不起作用,
(3)、不要在View中使用Handler
View本身提供了post系列的方法,完全可以替代handler。
(4)、内存泄漏的问题
记得在View中有动画的时候,退出的时候,调用下 onDetachedFromWindow。
(5)、带有滑动嵌套情形的时候,要处理好滑动冲突。