Android客户端 | 青训营笔记
这是我参与「第四届青训营 -Android场」笔记创作活动的的第4天
渲染
布局加载
LayoutInflate是一个用于加载布局的系统服务,就是实例化与Layout XML文件对应的View对象,称之为布局加载器,不能直接使用, 需要通过getLayoutInflater( )方法或getSystemService( )方法来获得与当前Context绑定的 LayoutInflater实例!
关于LayoutInflate的简单使用流程如下:
- 获取LayoutInflate的实例
- 调用加载布局的方法
- 通过LayoutInflate.LayoutParams设置相关属性
可以将createViewFromTag分成三个流程:
对一些特殊标签做处理,如view、TAG_1995
对Factory、Factory2的设置判断,如果设置了就会通过Factory、Factory2进行生成view
如果没有设置Factory、Factory2,就会使用LayoutInflater默认的方式,进行View的生成。
编写布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.andriud.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/subject"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/subject"/>
<EditText
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top"
android:hint="string/send">
注册Manifest
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android">
<activity android:name=".MainActivity">
<intent-filter>
<action.android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</mainifest>
设置布局文件
TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
// call the super class onCreate to complete the creation of activity like
//the view hierarchy
super.onCreate(savedInstanceState);
//set the user interface layout for this activity
// the layout file is defined in the project res/layout/main_activity.xml file
setContentView(R.layout.main_activity);
//initialize member TextView so we can manipulate it later
textView = (TextView) findViewById(R.id.text_view);
}
setContentView最终创建了DecorView,并由LayoutInflater来加载了XML文件
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor
.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
经过对布局加载原理的分析,可以看出布局加载的主要性能瓶颈在两个方面:
- 加载XML是一个IO过程,如果XML文件很大,就会相对比较耗时。
- View的实例时通过反射创建的,反射会比直接创建更耗时。
布局解析
LayoutInflater解析了XML文件,并根据XML文件生成了View实例,并将View实例添加了到了其ViewGroup中
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
while (((type = parser.next()) != XmlPullParser.END_TAG||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
// 省略
// 核心代码
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
小结
布局渲染
页面绘制
Vsync信号
UI渲染
渲染总结
交互
常用交互事件监听器
触摸事件
浏览器的触摸 API 由三个部分组成。
- Touch:一个触摸点
- TouchList:多个触摸点的集合
- TouchEvent:触摸引发的事件实例
Touch接口的实例对象用来表示触摸点(一根手指或者一根触摸笔),包括位置、大小、形状、压力、目标元素等属性。有时,触摸动作由多个触摸点(多根手指)组成,多个触摸点的集合由TouchList接口的实例对象表示。TouchEvent接口的实例对象代表由触摸引发的事件,只有触摸屏才会引发这一类事件。
很多时候,触摸事件和鼠标事件同时触发,即使这个时候并没有用到鼠标。这是为了让那些只定义鼠标事件、没有定义触摸事件的代码,在触摸屏的情况下仍然能用。如果想避免这种情况,可以用event.preventDefault方法阻止发出鼠标事件。
所有的交互事件都来自于对屏幕触摸信号的处理,View.OnClickListener()等常用点击事件是对交互事件的二次封装。
当用户触摸屏幕时,系统建立一系列的MotionEvent对象,MotionEvent包含关于发生触摸的位置和时间等细节信息,MotionEvenr对象被传递到相应的捕获函数中。
捕获触摸事件
public class MainActivity extends Activity {
@Override
public boolean onTouchEvent(MotionEvent event){
int action = MotionEventCompat.getActionMasked(event);
switch(action) {
case (MotionEvent.ACTION_DOWN) :
Log.d(DEBUG_TAG,"Action was DOWN");
return true;
case (MotionEvent.ACTION_MOVE) :
Log.d(DEBUG_TAG,"Action was MOVE");
return true;
case (MotionEvent.ACTION_UP) :
Log.d(DEBUG_TAG,"Action was UP");
return true;
case (MotionEvent.ACTION_CANCEL) :
Log.d(DEBUG_TAG,"Action was CANCEL");
return true;
case (MotionEvent.ACTION_OUTSIDE) :
Log.d(DEBUG_TAG,"Movement occurred outside bounds " + "of current screen element");
return true;
default :
return super.onTouchEvent(event);
}
}
Activity和View都有onTouchEvent(),用于处理触摸事件。
当用户触摸屏幕时,会回调触摸视图上的onTouchEvent()。 对于最终被识别为手势的每个轻触事件序列,onTouchEvent() 都会多次被触发。
触摸事件分发
事件处理流程
View的事件响应
public boollean onTouchEvent(MotionEvent event){
if(clickable || (viewFlags & TOOLTIO)==TOOLTIP){
switch(action){
case MotionEvent.ACTION_UP:
...
if(!mHasPerformedLongPress && !mIgnoreNextUpEvent){
removeLongPressCallback();
if(!focusTaken){
if(!post(mPerformClick)){
peiformClick();
}
}
}
}
break;
case MotionEvent.ACTION_DOWN:
if(isInScrollingContainer){
...
}else{
checkForLongClick(0,x,y);
}
break;
}
}
在onTouchEvent()的ACTION_DOWN设置了一个延时Runnable,用于处理onLongClickListener
在onTouchEvent()的ACTION_DOWN中,判断onLongClick是否执行,未执行则移除,然后执行onClickListener