Android客户端 | 青训营笔记

104 阅读4分钟

Android客户端 | 青训营笔记

这是我参与「第四届青训营 -Android场」笔记创作活动的的第4天

渲染

布局加载

LayoutInflate是一个用于加载布局的系统服务,就是实例化与Layout XML文件对应的View对象,称之为布局加载器,不能直接使用, 需要通过getLayoutInflater( )方法或getSystemService( )方法来获得与当前Context绑定的 LayoutInflater实例!

关于LayoutInflate的简单使用流程如下:

  1. 获取LayoutInflate的实例
  2. 调用加载布局的方法
  3. 通过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();
}

经过对布局加载原理的分析,可以看出布局加载的主要性能瓶颈在两个方面:

  1. 加载XML是一个IO过程,如果XML文件很大,就会相对比较耗时。
  2. 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);
    }
}

小结

image.png

布局渲染

页面绘制

image.png

Vsync信号

image.png

UI渲染

image.png

渲染总结

image.png

交互

常用交互事件监听器

image.png

触摸事件

浏览器的触摸 API 由三个部分组成。

  • Touch:一个触摸点
  • TouchList:多个触摸点的集合
  • TouchEvent:触摸引发的事件实例

Touch接口的实例对象用来表示触摸点(一根手指或者一根触摸笔),包括位置、大小、形状、压力、目标元素等属性。有时,触摸动作由多个触摸点(多根手指)组成,多个触摸点的集合由TouchList接口的实例对象表示。TouchEvent接口的实例对象代表由触摸引发的事件,只有触摸屏才会引发这一类事件。

很多时候,触摸事件和鼠标事件同时触发,即使这个时候并没有用到鼠标。这是为了让那些只定义鼠标事件、没有定义触摸事件的代码,在触摸屏的情况下仍然能用。如果想避免这种情况,可以用event.preventDefault方法阻止发出鼠标事件。

image.png 所有的交互事件都来自于对屏幕触摸信号的处理,View.OnClickListener()等常用点击事件是对交互事件的二次封装。

image.png 当用户触摸屏幕时,系统建立一系列的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() 都会多次被触发。

触摸事件分发

image.png

事件处理流程

image.png

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

总结

image.png