十七 、 View 的工作原理(1)--- View 绘制相关基础知识

首先我们看一下 Android 中的 UI 界面架构:

    上面这个图就是 Android 中的 UI 界面架构图,Activity 对象就是 Android System 提供的我们所看到的 UI 界面。从上图可以看到,每个 Activity 都包含一个 Window 对象(Window 类是抽象类,PhoneWindow 是 Window 类的具体实现类),在 Android 中 Window 对象通常由 PhoneWindow 来实现。PhoneWindow 将一个 DecorView 设置为整个应用窗口的根 ViewDecorView 作为窗口界面的顶层视图,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两部分,上面是 TitleView(标题栏),下面是 ContentView(内容栏)。这个 ContentView 其实就是一个 id 为 content 的 FramLayout。我们在创建一个活动时,在 onCreate() 方法中调用 setContentView(R.layout.activity_main); 时,其实就是将  activity_main.xml 布局文件设置到这个 FrameLayout 里。而 TitleView 多数情况下(不同版本,会有变化)就是默认显示的 ActionBar。

    我们知道,通过调用 requestWindowFeature(Window.FEATURE_NO_TITLE) 可以全屏显示界面,也就是没有标题栏,只剩下 ContentView。并且这个方法要在 setContentView 方法之前调用才能生效,下面是隐藏标题栏只显示 ContentView 的代码:

// 当继承自 Activity,并且 requestWindowFeature() 方法调用在 setContentView() 之前才会有效
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
    }
}

// 当继承自 AppCompatActivity 时,隐藏标题栏如下即可。
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(getSupportActionBar() != null){
            getSupportActionBar().hide();
        }
    }
}

    这里我们要注意,并不是调用了 setContentView() 方法之后我们就可以看到视图显示了。而是由 ActivityManagerService 回调 Activity 的 onResume() 方法之后,此时系统才会把整个 DecorView 添加到 PhoneWindow 中,并让其显示出来,从而最终完成界面的绘制。

    下面我们看一下,顶层视图 DecorView 怎么添加到 PhoneWindow 中的:

    当 Activity 初始化完 Window 并将布局添加到 PhoneWindow 的内部类 DecorView 之后,ActivityThread 类会调用 handleResumeActivity() 方法将顶层视图 DecorView 添加到 PhoneWindow 中:

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
            if (r.window == null && !a.mFinished && willBeVisible) {
                // 获取当前 activity 的 PhoneWindow 对象
                r.window = r.activity.getWindow();

                // 获取 PhoneWindow 对象的 DecorView(也就是顶层视图)
                View decor = r.window.getDecorView();

                // 设置 DecorView 不可见
                decor.setVisibility(View.INVISIBLE);

                // 获取当前 activity 的 WindowManagerImpl 对象
                ViewManager wm = a.getWindowManager();
               ...
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        // 标记 DecorView 已经添加到 PhoneWindow
                        a.mWindowAdded = true;
                        // WindowManagerImpl 将 DecorView 添加到 PhoneWindow 中
                        wm.addView(decor, l);
                    } else {
                        ...
                    }
                }
            ...
    }
}

    可以看到最终是通过 WindowManagerImpl 类的 addView() 方法:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    ...

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    ...    
}

    继续追踪 WindowManagerGlobal 类中的 addView():

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    ViewRootImpl root; 

    ...
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);

    try {
        // 将 DecorView 设置到 ViewRootImpl 对象中
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        ...
    }
}

    继续追踪 ViewRootImpl 类中的 setView() 方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    // 将传过来的 DecorView 赋值给全局 mView 参数
    mView = view;

    // 标记已经添加了 DecorView 到窗口
    mAdded = true;
    ...

    // 请求开始布局
    requestLayout();
    ...
}

    看一下 requestLayout() 方法:

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

    再继续到  scheduleTraversals() 方法:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        ...
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ...
    }
}

    再继续执行 mTraversalRunnable 中的  doTraversal() 方法:

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {

    @Override
    public void run() {
        doTraversal();
    }
}

    继续追踪 doTraversal() 方法:

void doTraversal() {
    if (mTraversalScheduled) {
        ...
        try {
            // 最终调用 performTraversals() 方法开始真正的 View 绘制流程
            performTraversals();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
}

    最后看一下 ViewRootImpl 中的 performTraversals() 方法:

private void performTraversals() {
    // 这个 mView 就是 DecorView 根布局
    final View host = mView;

    // mAdded赋值为true,因此条件不成立
    if (host == null || !mAdded)
        return;

    //是否正在遍历
    mIsInTraversal = true;

    //是否马上绘制View
    mWillDrawSoon = true;

    //顶层视图DecorView所需要窗口的宽度和高度
    int desiredWindowWidth;
    int desiredWindowHeight;

    //在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
    if (mFirst) {
        mFullRedrawNeeded = true;
        mLayoutRequested = true;

        //如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要窗口的宽度和高度就是除了状态栏
        if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
            Point size = new Point();
            mDisplay.getRealSize(size);
            desiredWindowWidth = size.x;
            desiredWindowHeight = size.y;
        } else {
            //否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高
            DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics();
            desiredWindowWidth = packageMetrics.widthPixels;
            desiredWindowHeight = packageMetrics.heightPixels;
        }
    }
    ...

    //获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

    //执行测量操作
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...

    //执行布局操作
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...

    //执行绘制操作
    performDraw();

}

    通过上面的源码追踪,我们可以知道,View 的绘制流程是从 ViewRootImpl 对象中的 performTraversals() 方法开始的 ViewRootImpl 对象是连接 WindowManager 和 DecorView 的桥梁。在 performTraversals() 方法内部,经过 measure(测量)、layout(布局)、draw(绘制)三个过程才能最终将一个 View 绘制出来,其中 measure 用来测量 View 的宽和高,layout 用来确定 View 在父容器中的放置位置,而 draw 则负责将 View 绘制在屏幕上

    通过 performTraversals() 方法我们可以看到,它最后会依次调用 performMeasure()、performLayout()、performDraw() 这三个方法,这三个方法分别完成顶级 View 的 measure、layout 和 draw 这三大流程,其中在 performMeasure 中会调用 measure() 方法 -> 接着调用 onMeasure() 方法 -> 接着对所有的子元素进行 measure 过程,这个时候 measure 流程就从父容器传递到子元素中了,这样就完成了一次 measure 过程(父容器的 measure 过程完成)。接着子元素会重复父容器的 measure 过程,如此反复就完成了整个 View 树的遍历。同理,performLayout() 和 performDraw() 的传递流程和 performMeasure() 是类似的,唯一不同的是,performDraw() 的传递过程是在 draw() 方法中通过 dispatchDraw() 来实现的,不过这并没有本质区别。

    measure 过程决定了 View 的宽和高,measure 完成以后,可以通过 getMeasuredWidth() 和 geyMeasuredHeight() 方法来获取到 View 的宽/高,在几乎所有的情况下它都等同 View 的最终的宽/高,当然有特殊情况,后面会学习。

    layout 过程决定了 View 的四个顶点的坐标和实际的 View 的宽/高,完成以后,可以通过getTop()、getBottom()、getLeft() 和 getRight() 来拿到四个顶点的位置,并可以通过 getWidth() 和 getHeight() 方法来拿到 View 的最终宽/高

    draw 过程则最终决定了 View 的显示,只有 draw 方法完成以后,View 的内容才能呈现在屏幕上。

    下面是 ViewRootImpl 对象的 performTraversals() 方法的工作流程图:

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android Studio可以使用Canvas和Paint类来绘制曲线。具体步骤如下: 1. 创建一个自定义View类,继承自View或其子类,例如SurfaceView。 2. 在onDraw方法中,使用Canvas和Paint类来绘制曲线。可以使用Path类来定义曲线的路径,然后使用Canvas的drawPath方法来绘制曲线。 3. 可以使用Paint类来设置曲线的颜色、宽度、样式等属性。 4. 在Activity中,将自定义View添加到布局中。 5. 在自定义View中,可以通过触摸事件来实现用户交互,例如让用户在屏幕上绘制曲线。 需要注意的是,绘制曲线需要一定的数学基础,需要了解曲线的方程和参数化等知识。同时,绘制曲线也需要一定的计算能力,如果曲线过于复杂,可能会影响性能。 ### 回答2: Android Studio是一款非常流行的开发工具,可以让我们在Android设备上创建出色的应用程序。绘制曲线是Android Studio一个非常有用的功能,这个功能可以让我们创建各种华丽的视觉效果,增强我们的应用程序的用户体验。以下是关于如何在Android Studio中绘制曲线的详细介绍。 首先,我们需要了解如何使用Java编写曲线代码。在Java中,曲线可以通过一组点来表示,我们可以使用Bezier曲线或Cardinal曲线来绘制。贝塞尔曲线是一组平滑的曲线,由若干个点组成。使用Cardinal曲线也可以创建平滑的曲线,在实现细节上略有不同。我们可以使用Java图形库中的Path类来绘制曲线。 在Android Studio中,我们可以使用Canvas和Paint类来绘制曲线。Canvas类允许我们在屏幕上创建绘制对象,Paint类则允许我们指定放置画笔的样式,例如线条颜色和宽度等。我们可以通过创建一个自定义视图,并覆盖该视图的onDraw()方法来实现曲线绘制的功能。 什么是自定义视图?简单来说,该视图是我们自己创建的,可以包含我们想要显示在屏幕上的所有元素。我们可以使用该视图来绘制曲线,也可以将该视图添加到主布局上,以便用户可以看到该视图和曲线。 现在,我们来看看如何在自定义视图中绘制曲线。我们首先需要创建自定义视图类。在这个类中,我们需要实现onDraw()方法,并在其中编写绘制代码。以下是一个简单的自定义视图类: ``` public class DrawView extends View { Paint paint = new Paint(); public DrawView(Context context) { super(context); } public DrawView(Context context, AttributeSet attrs) { super(context, attrs); } public DrawView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setStyle(Paint.Style.FILL_AND_STROKE); paint.setColor(Color.RED); //指定画笔颜色 paint.setStrokeWidth(5); //指定画笔宽度 // 创建路径对象 Path path = new Path(); // 画曲线 path.moveTo(10, 10); //起始点 path.quadTo(100, 50, 200, 10); //二次贝塞尔曲线 path.quadTo(250, 70, 400, 10); //二次贝塞尔曲线 path.quadTo(450, 70, 500, 10); //二次贝塞尔曲线 //在画布上绘制路径 canvas.drawPath(path, paint); } } ``` 在上面的代码中,我们首先创建了一个Paint对象,该对象包含一些绘制画笔的属性(例如颜色和宽度)。然后,我们创建了一个新的Path对象,该对象将用于保存曲线路径。接下来,我们使用贝塞尔曲线方法(quadTo())在路径对象中添加几个点。最后,我们在画布对象上运用自己创建的绘制样式来绘制路径。 我们还需要在布局中添加自定义视图。我们可以在XML布局文件中添加一个View对象,并使用custom_view.xml作为样式文件,然后在Activity的onCreate()方法中设置布局文件。请看下面的例子: ``` <!-- activity_main.xml --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.example.myapplication.DrawView android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> ``` 在上面的代码中,我们在RelativeLayout中添加了自定义视图(DrawView),使其占据整个布局界面。 最后,我们调用Activity的onCreate()方法,并执行以下代码: ``` public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } ``` 运行应用程序时,我们将看到曲线绘制在屏幕上。如果需要改变曲线的颜色或位置等属性,我们可以通过更改Path和Paint对象来实现。 ### 回答3: Android Studio是Android开发领域中最流行的开发环境之一。其中,绘制曲线是Android Studio中的一个非常重要的功能。绘制曲线可以在许多应用中被使用,例如绘制折线图、曲线图等等。 在Android Studio中绘制曲线的主要方式是使用Canvas和Paint。Canvas是用于绘制图形的基本对象,而Paint则是用于绘制图形的属性设置对象。这两个对象都可以在Java代码中创建和配置。 绘制曲线的第一步是使用Canvas对象创建一个新的路径(Path)。路径是可以定义曲线的对象,可以使用它来描述曲线的形状、轮廓和方向。创建一个新的路径的代码如下: Path path = new Path(); 接下来就是要将曲线的点添加到路径中。在路径中添加点的代码如下: path.moveTo(x1, y1); path.lineTo(x2, y2); path.quadTo(x3, y3, x4, y4); 在上面的代码中,moveTo方法用于将路径移动到第一个点(x1, y1)。lineTo方法则用于连接第一个点和第二个点(x2, y2)。quadTo方法是用于绘制曲线的方法,用于将路径连接到第三个点(x3, y3),通过第四个点(x4, y4)来控制曲线的形状。 完成曲线的绘制后,我们需要在Canvas对象上使用Paint对象绘制出这个路径。我们可以通过指定Paint对象的颜色、线宽等属性,来控制曲线的外观。如下所示: Paint paint = new Paint(); paint.setColor(Color.RED); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.STROKE); canvas.drawPath(path, paint); 在上述代码中,我们首先创建了一个新的Paint对象,并设置了它的颜色(Color.RED)、线宽(5)和线型(Paint.Style.STROKE)等属性。然后,使用Canvas对象的drawPath方法绘制出我们的路径。 绘制曲线的过程对于初学者来说可能有一定的难度,但是通过阅读Android Studio的文档和教程,我们可以了解基本的绘制曲线的方法和技巧,并且在实践中不断提高能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值