-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
252 lines (249 loc) · 72.3 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>View的布局</title>
<url>/2019/11/25/View%E7%9A%84%E5%B8%83%E5%B1%80/</url>
<content><![CDATA[<h2 id="View的布局"><a href="#View的布局" class="headerlink" title="View的布局"></a>View的布局</h2><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>上节我们学完了<a href="">View的measure</a>,知道了每个View的自身的大小和一组控件的大小,就好比小时候我们玩的拼图,我们现在把每一块拼图的大小测量完成了,但是这一堆拼图他们是杂乱无序的,我们期望的不是看到一堆杂乱无章的小拼块,而是一个完整的拼图,因此我们就需要将他们拼接起来,使他们成为一个完整的拼图,拼接的过程就是我们layout的过程,对于单个View来说,因为只有它自己一个,摆在那里都行,但是对于一组View(拼块),我们就需要对它进行layout,使它成为一个完整的View(大的拼图),因此View的布局针对的是ViewGroup而不是View, 接下来我们就一起来看看View的布局。</p>
<p>在分析之前我给大家提个问题,<strong>你在玩拼图的时候是怎么拼的呢?</strong>,先思考几秒在进入我们今天的正题</p>
<p>在分析布局的时候我们还是将布局的起点的代码给出来,大家在重新过一遍</p>
<h2 id="Layout的起点"><a href="#Layout的起点" class="headerlink" title="Layout的起点"></a>Layout的起点</h2><p>layout的起点是从ViewRootImpl的requestLayout()开始的,中间的部分过程我就省略了,不熟悉的可以看我的<a href="">View的measure</a></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"> public void requestLayout() {</span><br><span class="line"> if (!mHandlingLayoutInLayoutRequest) { //注意该标记,layout时会用到</span><br><span class="line"> checkThread();</span><br><span class="line"> mLayoutRequested = true; //true将进行measure和layout</span><br><span class="line"> scheduleTraversals();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> scheduleTraversals()内部会调用performTraversals()</span><br><span class="line"> </span><br><span class="line"> private void performTraversals() {</span><br><span class="line"> ....</span><br><span class="line"> // 满足任意添加就会进行测量</span><br><span class="line"> if (mFirst || windowShouldResize || insetsChanged ||</span><br><span class="line"> viewVisibilityChanged || params != null || mForceNextWindowRelayout) { </span><br><span class="line"> int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); </span><br><span class="line"> int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);</span><br><span class="line"> //执行测量</span><br><span class="line"> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); </span><br><span class="line"> }</span><br><span class="line"> ....</span><br><span class="line"> </span><br><span class="line"> final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);</span><br><span class="line"> //测量走了,layout方法一般也会走</span><br><span class="line"> if (didLayout) {</span><br><span class="line"> //在这里进行布局</span><br><span class="line"> performLayout(lp, mWidth, mHeight);</span><br><span class="line"> }</span><br><span class="line"> ....</span><br><span class="line"> </span><br><span class="line"> boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;</span><br><span class="line"> if (!cancelDraw && !newSurface) {</span><br><span class="line"> //在这里进行页面的绘制</span><br><span class="line"> performDraw();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>测量已经讲过,接下来我们就分析performLayout(), 我们下来看看的源码</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {</span><br><span class="line"> mLayoutRequested = false;</span><br><span class="line"> mInLayout = true; //是否在布局的标志位</span><br><span class="line"> final View host = mView; //这里的host其实就是decorView</span><br><span class="line"> </span><br><span class="line"> //第一次进行layout,调用ViewGroup的layout</span><br><span class="line"> host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());</span><br><span class="line"> //第一次layout结束</span><br><span class="line"> mInLayout = false;</span><br><span class="line"> </span><br><span class="line"> //第一次layout后查看还有多少view重新请求layout</span><br><span class="line"> int numViewsRequestingLayout = mLayoutRequesters.size(); </span><br><span class="line"> if (numViewsRequestingLayout > 0) {</span><br><span class="line"> ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,false);</span><br><span class="line"> if (validLayoutRequesters != null) {</span><br><span class="line"> </span><br><span class="line"> //该标记置为true,其不会走requestLayout(),这样就不会在测量父布局</span><br><span class="line"> mHandlingLayoutInLayoutRequest = true; </span><br><span class="line"> </span><br><span class="line"> int numValidRequests = validLayoutRequesters.size();</span><br><span class="line"> for (int i = 0; i < numValidRequests; ++i) {</span><br><span class="line"> final View view = validLayoutRequesters.get(i);</span><br><span class="line"> view.requestLayout(); //最终调用的还是viewRootImpl得requestLayout(),此时mHandlingLayoutInLayoutRequest为true, 不会走scheduleTraversals(); 再次测量</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> //再次测量</span><br><span class="line"> measureHierarchy(host,lp,mView.getContext().getResources(),</span><br><span class="line"> desiredWindowWidth,desiredWindowHeight);</span><br><span class="line"> mInLayout = true;</span><br><span class="line"> </span><br><span class="line"> //进行第二次布局</span><br><span class="line"> host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());</span><br><span class="line"> mHandlingLayoutInLayoutRequest = false;</span><br><span class="line"> </span><br><span class="line"> //在此检查是否还有view需要layout,如果有将其添加到队列中,下次在用</span><br><span class="line"> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);</span><br><span class="line"> if (validLayoutRequesters != null) {</span><br><span class="line"> final ArrayList<View> finalRequesters = validLayoutRequesters;</span><br><span class="line"> getRunQueue().post(new Runnable() { // Post second-pass requests to the next frame</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> int numValidRequests = finalRequesters.size();</span><br><span class="line"> for (int i = 0; i < numValidRequests; ++i) {</span><br><span class="line"> final View view = finalRequesters.get(i);</span><br><span class="line"> Log.w("View", "requestLayout() improperly called by " + view +</span><br><span class="line"> " during second layout pass: posting in next frame");</span><br><span class="line"> view.requestLayout(); </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> //第二次布局结束</span><br><span class="line"> mInLayout = false;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>performLayout()的代码不是很多,在代码里面我们发现他进行了两次layout,为什么会进行两次layout呢?第二此的layout是从mLayoutRequesters这个集合开始判断的,它是一个ArrayList集合,我们看看它是在哪里赋值的,通过查找我们发现了它是在requestLayoutDuringLayout()这里赋值的</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">boolean requestLayoutDuringLayout(final View view) {</span><br><span class="line"> if (view.mParent == null || view.mAttachInfo == null) {</span><br><span class="line"> // Would not normally trigger another layout, so just let it pass through as usual</span><br><span class="line"> return true;</span><br><span class="line"> }</span><br><span class="line"> if (!mLayoutRequesters.contains(view)) {</span><br><span class="line"> mLayoutRequesters.add(view);</span><br><span class="line"> }</span><br><span class="line"> if (!mHandlingLayoutInLayoutRequest) {</span><br><span class="line"> // Let the request proceed normally; it will be processed in a second layout pass</span><br><span class="line"> // if necessary</span><br><span class="line"> return true;</span><br><span class="line"> } else {</span><br><span class="line"> // Don't let the request proceed during the second layout pass.</span><br><span class="line"> // It will post to the next frame instead.</span><br><span class="line"> return false;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>哪这个方法又是在哪里调用的呢?我们发现它只有一个地方调用,那就是view的requestLayout()中进行调用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public void requestLayout() {</span><br><span class="line"> if (mMeasureCache != null) mMeasureCache.clear();</span><br><span class="line"></span><br><span class="line"> if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {</span><br><span class="line"> // Only trigger request-during-layout logic if this is the view requesting it,</span><br><span class="line"> // not the views in its parent hierarchy</span><br><span class="line"> ViewRootImpl viewRoot = getViewRootImpl();</span><br><span class="line"> if (viewRoot != null && viewRoot.isInLayout()) {</span><br><span class="line"> if (!viewRoot.requestLayoutDuringLayout(this)) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> mAttachInfo.mViewRequestingLayout = this;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> mPrivateFlags |= PFLAG_FORCE_LAYOUT;</span><br><span class="line"> mPrivateFlags |= PFLAG_INVALIDATED;</span><br><span class="line"></span><br><span class="line"> if (mParent != null && !mParent.isLayoutRequested()) {</span><br><span class="line"> mParent.requestLayout();</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>从requestLayoutDuringLayout()这个方法名我们知道这个方法的调用时机是当我们在layout的时候有View调用了requestLayout(), 所以既然有View在layout的时候调用了requestLayout那我么就必须进行重新布局,这就是为什么会有二次布局,接下来我们就看看layout()做了什么</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">ViewGroup的layout()内部其实还是调用的是View的layout</span><br><span class="line"></span><br><span class="line">public void layout(int l, int t, int r, int b) {</span><br><span class="line"> if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {</span><br><span class="line"> onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);</span><br><span class="line"> mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> boolean changed = isLayoutModeOptical(mParent) ? 对不同模式分别调用对象的方法,作用是设置View的四个点</span><br><span class="line"> setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);</span><br><span class="line"> </span><br><span class="line"> if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {</span><br><span class="line"> onLayout(changed, l, t, r, b); //调用onLayout()</span><br><span class="line"> </span><br><span class="line"> //调用layout完成的监听</span><br><span class="line"> ListenerInfo li = mListenerInfo;</span><br><span class="line"> if (li != null && li.mOnLayoutChangeListeners != null) {</span><br><span class="line"> ArrayList<OnLayoutChangeListener> listenersCopy =</span><br><span class="line"> (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();</span><br><span class="line"> int numListeners = listenersCopy.size();</span><br><span class="line"> for (int i = 0; i < numListeners; ++i) {</span><br><span class="line"> istenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>layout()内部调用的是onLayout(), 该方法是抽象的,因为具体怎么放置只有我们自己清楚,系统是没法帮我们layout的,所以接下来我们就看看它的子类值怎么实现的</p>
<h3 id="FramLayout-onLayout"><a href="#FramLayout-onLayout" class="headerlink" title="FramLayout onLayout()"></a>FramLayout onLayout()</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">protected void onLayout(boolean changed, int left, int top, int right, int bottom) {</span><br><span class="line"> layoutChildren(left, top, right, bottom, false /* no force left gravity */);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {</span><br><span class="line"> final int count = getChildCount();</span><br><span class="line"></span><br><span class="line"> final int parentLeft = getPaddingLeftWithForeground();</span><br><span class="line"> final int parentRight = right - left - getPaddingRightWithForeground();</span><br><span class="line"></span><br><span class="line"> final int parentTop = getPaddingTopWithForeground();</span><br><span class="line"> final int parentBottom = bottom - top - getPaddingBottomWithForeground();</span><br><span class="line"></span><br><span class="line"> for (int i = 0; i < count; i++) {</span><br><span class="line"> final View child = getChildAt(i);</span><br><span class="line"> if (child.getVisibility() != GONE) {</span><br><span class="line"> final LayoutParams lp = (LayoutParams) child.getLayoutParams();</span><br><span class="line"></span><br><span class="line"> final int width = child.getMeasuredWidth();</span><br><span class="line"> final int height = child.getMeasuredHeight();</span><br><span class="line"></span><br><span class="line"> int childLeft;</span><br><span class="line"> int childTop;</span><br><span class="line"></span><br><span class="line"> int gravity = lp.gravity;</span><br><span class="line"> if (gravity == -1) {</span><br><span class="line"> gravity = DEFAULT_CHILD_GRAVITY;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> final int layoutDirection = getLayoutDirection();</span><br><span class="line"> final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);</span><br><span class="line"> final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;</span><br><span class="line"></span><br><span class="line"> switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {</span><br><span class="line"> case Gravity.CENTER_HORIZONTAL:</span><br><span class="line"> childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +</span><br><span class="line"> lp.leftMargin - lp.rightMargin;</span><br><span class="line"> break;</span><br><span class="line"> case Gravity.RIGHT:</span><br><span class="line"> if (!forceLeftGravity) {</span><br><span class="line"> childLeft = parentRight - width - lp.rightMargin;</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> case Gravity.LEFT:</span><br><span class="line"> default:</span><br><span class="line"> childLeft = parentLeft + lp.leftMargin;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> switch (verticalGravity) {</span><br><span class="line"> case Gravity.TOP:</span><br><span class="line"> childTop = parentTop + lp.topMargin;</span><br><span class="line"> break;</span><br><span class="line"> case Gravity.CENTER_VERTICAL:</span><br><span class="line"> childTop = parentTop + (parentBottom - parentTop - height) / 2 +</span><br><span class="line"> lp.topMargin - lp.bottomMargin;</span><br><span class="line"> break;</span><br><span class="line"> case Gravity.BOTTOM:</span><br><span class="line"> childTop = parentBottom - height - lp.bottomMargin;</span><br><span class="line"> break;</span><br><span class="line"> default:</span><br><span class="line"> childTop = parentTop + lp.topMargin;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> child.layout(childLeft, childTop, childLeft + width, childTop + height);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>FrameLayout的onLayout()代码比较简单,相信大家都能明白,最终内部依然调用的是子类的layout,至此View的layout原理我们已经分析完了。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p> 我们在来回顾一下本节的知识点,View的layout从performLayout()开始,先进行第一次的layout(), 完了之后在判断在layout的过程中有没有View调用了requestLayout(), 如果有的话就进行第二次重新测量和布局,第二次布局完成之后我们在去检查一下是否还有view请求了requestLayout(), 如果还要的话就将它加入的队列中,等到下一下布局的时候在重新测量布局,在layout方法中,就是将view的宽高换算成坐标,在调用onLayout(),有用户来确定这些view该怎么摆放。然后我们有查看了FrameLayout的onLayout,其内部就是通过我们xml文件里面设置的一系列的gravity及padding值来设置摆放view的位置的。为了方便大家的记忆我把整个流程做了一张图片,大家看看就一目了然了</p>
<p><img src="/Users/apple/Desktop/blog/View%E7%9A%84%E5%B8%83%E5%B1%80.png" alt=""></p>
<p>接下来我们就进入<a href="">View的绘制draw</a></p>
]]></content>
<tags>
<tag>Android View的布局</tag>
</tags>
</entry>
<entry>
<title>View的测量</title>
<url>/2019/11/25/View%E7%9A%84%E6%B5%8B%E9%87%8F/</url>
<content><![CDATA[<h2 id="View的测量"><a href="#View的测量" class="headerlink" title="View的测量"></a>View的测量</h2><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p> 我们做Android开发的目的就是把产品设计的东西运行到我们的手机上,说白了就是把设计图搬到我们的手机上,其中跟我们打交道最多的就是View, 所以我们有必要了解它是个什么东西,又是怎么把我们需要的东西展现在手机屏幕上,这就涉及到了View的测量、布局和绘制。这里我将分三个章节来一一向你展示,本节我们先来分析View的测量。在分析源码之前,我们先问自己几个问题,什么是测量?测量是干什么的?那些东西需要测量?从哪里开始测量?带着这些问题我们开始进行今天的答疑之旅</p>
<ol>
<li><p>测量是什么?它是干什么的?</p>
<p> 测量就是描述一个物体所占据的空间,也就是它的长宽高,是一个物体本身的属性</p>
</li>
<li><p>为什么需要测量</p>
<p> 回答这个问题之前我们要明白我们要干什么,我们是要把我们的xml文件里面的view绘制到手机屏幕上,既然我们要把view绘制到屏幕上那我们总得知道我们绘制的view有多大吧,这就是我们为什么需要测量</p>
</li>
<li><p>那些东西需要测量?</p>
<p> 我们绘制到屏幕上的view要么是单个View,要么是一组View即ViewGroup,因此需要测量的也就是View和ViewGroup</p>
</li>
<li><p>从哪里开始测量?</p>
<p>解答了上面的问题接下来我将为大家讲解从哪里开始测量以及单个View和ViewGroup(一组View)怎么测量</p>
</li>
</ol>
<h2 id="从哪里开始测量"><a href="#从哪里开始测量" class="headerlink" title="从哪里开始测量"></a>从哪里开始测量</h2><p> 刚我也说了我们测量是要把xml文件中的view,添加绘制到手机屏幕上,所以测量的开始从添加view开始,通过Activity的启动流程到View的显示我们知道,view的添加是在ViewRootImpl中进行的,调用的是addView() -> setView() -> requestLayout(),不明白的可以看我之前的文章,接下来我们从requestLayout()开始分析</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public void requestLayout() {</span><br><span class="line"> if (!mHandlingLayoutInLayoutRequest) {</span><br><span class="line"> checkThread();</span><br><span class="line"> mLayoutRequested = true; //大家在这里先记住这个标记,在后面很重要</span><br><span class="line"> scheduleTraversals();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">void scheduleTraversals() { </span><br><span class="line"> .... 省略部分代码</span><br><span class="line"> mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">final class TraversalRunnable implements Runnable {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> doTraversal();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">void doTraversal() {</span><br><span class="line"> performTraversals(); </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>通过查看源码发现其内部调用的是performTraversals(), 这又是何方神圣呢?我们进入代码在仔细看个清楚</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">private void performTraversals() {</span><br><span class="line"> //注意 mLayoutRequested直接决定了layoutRequested,也在很大程度上决定了performMeasure()</span><br><span class="line"> boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);</span><br><span class="line"> if (layoutRequested) {</span><br><span class="line"> windowSizeMayChange |= measureHierarchy(host, lp, res,</span><br><span class="line"> desiredWindowWidth, desiredWindowHeight); </span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> boolean windowShouldResize = layoutRequested && windowSizeMayChange</span><br><span class="line"> && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())</span><br><span class="line"> || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&</span><br><span class="line"> frame.width() < desiredWindowWidth && frame.width() != mWidth)</span><br><span class="line"> || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&</span><br><span class="line"> frame.height() < desiredWindowHeight && frame.height() != mHeight));</span><br><span class="line"> </span><br><span class="line"> // 满足任意添加就会进行测量</span><br><span class="line"> if (mFirst || windowShouldResize || insetsChanged ||</span><br><span class="line"> viewVisibilityChanged || params != null || mForceNextWindowRelayout) { </span><br><span class="line"> //获取顶层布局的宽高测量规格,执行测量方法</span><br><span class="line"> int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); </span><br><span class="line"> int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);</span><br><span class="line"> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); </span><br><span class="line"> }</span><br><span class="line"> ....</span><br><span class="line"> </span><br><span class="line"> final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);</span><br><span class="line"> //测量走了,layout方法一般也会走</span><br><span class="line"> if (didLayout) {</span><br><span class="line"> performLayout(lp, mWidth, mHeight);</span><br><span class="line"> }</span><br><span class="line"> ....</span><br><span class="line"> </span><br><span class="line"> boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;</span><br><span class="line"> if (!cancelDraw && !newSurface) {</span><br><span class="line"> //在这里进行页面的绘制</span><br><span class="line"> performDraw();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>原来我们的测量、布局和绘制都是在这个方法里面完成的,今天我们分析的是测量,布局和绘制等到下个章节在讲,接下来我们就看看performMeasure()</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {</span><br><span class="line"> if (mView == null) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");</span><br><span class="line"> try {</span><br><span class="line"> //注意这个mView其实就是decorView, 本质上是一个ViewGroup</span><br><span class="line"> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);</span><br><span class="line"> } finally {</span><br><span class="line"> Trace.traceEnd(Trace.TRACE_TAG_VIEW);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>接下来会走到ViewGroup的measure(), 通过源码发现ViewGroup没有measure(), 所以我们只有查看它的父类View, 我们来看看View的measure()做了什么</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public final void measure(int widthMeasureSpec, int heightMeasureSpec) {</span><br><span class="line"> boolean optical = isLayoutModeOptical(this);</span><br><span class="line"> if (optical != isLayoutModeOptical(mParent)) {</span><br><span class="line"> Insets insets = getOpticalInsets();</span><br><span class="line"> int oWidth = insets.left + insets.right;</span><br><span class="line"> int oHeight = insets.top + insets.bottom;</span><br><span class="line"> widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);</span><br><span class="line"> heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);</span><br><span class="line"> }</span><br><span class="line"> onMeasure(widthMeasureSpec, heightMeasureSpec);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>View的measure方法判断了一下View的模式,就直接调用onMeasure(), 是不是觉得很熟悉,没错我们自定义view的时候就需要复写该方法,之前我们也说过测量有View的测量和ViewGroup的测量,我们先来看看View的测量即onMeasure()</p>
<h3 id="View的测量-1"><a href="#View的测量-1" class="headerlink" title="View的测量"></a>View的测量</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {</span><br><span class="line"> setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),</span><br><span class="line"> getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>View的onMeasure()中得到一个默认值就直接设置给了view,在获取默认值时调用了getSuggestedMinimumWidth(),我们来看看这个方法里面做了什么</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">protected int getSuggestedMinimumWidth() {</span><br><span class="line"> return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>根据方法名我们知道得到的是一个建议的最小值,首先我们要明白mBackground是谁,mBackground是我们给view设置的背景,为什么要在这里判断mBackground是否为null呢?这是因为如果我们给view设置了背景,为了凸显出该view设置了背景,系统就会给背景设置一个最小宽度。 mMinWidth这是从哪里来的呢?别急这个值是你自己设置的,我们写的xml文件在给手机屏幕做适配的时候会用到,如:android:minWidth=”20dp”,如果不设置的话默认为0。我们在看看getDefaultSize()</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public static int getDefaultSize(int size, int measureSpec) {</span><br><span class="line"> int result = size;</span><br><span class="line"> int specMode = MeasureSpec.getMode(measureSpec);</span><br><span class="line"> int specSize = MeasureSpec.getSize(measureSpec);</span><br><span class="line"></span><br><span class="line"> switch (specMode) {</span><br><span class="line"> case MeasureSpec.UNSPECIFIED:</span><br><span class="line"> result = size;</span><br><span class="line"> break;</span><br><span class="line"> case MeasureSpec.AT_MOST:</span><br><span class="line"> case MeasureSpec.EXACTLY:</span><br><span class="line"> result = specSize;</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>注意这里AT_MOST和EXACTLY模式下得到的值是一样的,接下来我们再来看看setMeasuredDimension()设置测量的view的宽高</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {</span><br><span class="line"> boolean optical = isLayoutModeOptical(this);</span><br><span class="line"> //判断父view是否有视觉模式,如果有在加上视觉模式所需要的宽高</span><br><span class="line"> if (optical != isLayoutModeOptical(mParent)) { </span><br><span class="line"> Insets insets = getOpticalInsets();</span><br><span class="line"> int opticalWidth = insets.left + insets.right;</span><br><span class="line"> int opticalHeight = insets.top + insets.bottom;</span><br><span class="line"></span><br><span class="line"> measuredWidth += optical ? opticalWidth : -opticalWidth;</span><br><span class="line"> measuredHeight += optical ? opticalHeight : -opticalHeight;</span><br><span class="line"> }</span><br><span class="line"> setMeasuredDimensionRaw(measuredWidth, measuredHeight);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { //最终在这里对view的宽高进行保留</span><br><span class="line"> mMeasuredWidth = measuredWidth;</span><br><span class="line"> mMeasuredHeight = measuredHeight;</span><br><span class="line"></span><br><span class="line"> mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>在这里会保存测量的宽高,后面我们使用的getMeasureWidth()、getMeasureHight()得到的值就是在这里保留的值。至此view的测量到此结束,我们来看看ViewGroup的测量吧,ViewGroup是一个抽象类,onMeasure()也是抽象的,我们看看他的子类,我们知道Android有四大布局,即FrameLayout、RelativeLayout、LinearLayout及AbsoultLayout, AbsoultLayout绝对布局,这个我们很少用到,FrameLayout是最简单的布局,我们就先看看它内部是怎么实现的。</p>
<h3 id="ViewGroup的测量"><a href="#ViewGroup的测量" class="headerlink" title="ViewGroup的测量"></a>ViewGroup的测量</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {</span><br><span class="line"> //1、获取所有的子类</span><br><span class="line"> int count = getChildCount();</span><br><span class="line"></span><br><span class="line"> //2、判断子类里面的宽高是否有设置match_parent</span><br><span class="line"> final boolean measureMatchParentChildren =</span><br><span class="line"> MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||</span><br><span class="line"> MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;</span><br><span class="line"> //将存放width或height为match_parent的view集合清空 </span><br><span class="line"> mMatchParentChildren.clear();</span><br><span class="line"></span><br><span class="line"> //3、定义变量存储获取的子类的最大宽高</span><br><span class="line"> int maxHeight = 0;</span><br><span class="line"> int maxWidth = 0;</span><br><span class="line"> int childState = 0;</span><br><span class="line"></span><br><span class="line"> //4、遍历所有的子类,获得其宽高,取出子类中最大的宽和高</span><br><span class="line"> for (int i = 0; i < count; i++) {</span><br><span class="line"> final View child = getChildAt(i);</span><br><span class="line"> if (mMeasureAllChildren || child.getVisibility() != GONE) {</span><br><span class="line"> measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);</span><br><span class="line"> final LayoutParams lp = (LayoutParams) child.getLayoutParams();</span><br><span class="line"> maxWidth = Math.max(maxWidth,</span><br><span class="line"> child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);</span><br><span class="line"> maxHeight = Math.max(maxHeight,</span><br><span class="line"> child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);</span><br><span class="line"> childState = combineMeasuredStates(childState, child.getMeasuredState());</span><br><span class="line"> //5、如果子类的宽高有设置match_parent,则将其添加到数组中</span><br><span class="line"> if (measureMatchParentChildren) {</span><br><span class="line"> if (lp.width == LayoutParams.MATCH_PARENT ||</span><br><span class="line"> lp.height == LayoutParams.MATCH_PARENT) {</span><br><span class="line"> mMatchParentChildren.add(child);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //6、将子类获得的最大值加上父类设置的padding值,得到一个最大值</span><br><span class="line"> maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();</span><br><span class="line"> maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();</span><br><span class="line"></span><br><span class="line"> // 7、再次检验,将获得的最大值和建议的最小值进行比较取得最终的最值</span><br><span class="line"> maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());</span><br><span class="line"> maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());</span><br><span class="line"></span><br><span class="line"> // Check against our foreground's minimum height and width</span><br><span class="line"> final Drawable drawable = getForeground();</span><br><span class="line"> if (drawable != null) {</span><br><span class="line"> maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());</span><br><span class="line"> maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 8、设置frameLayout的宽高,frameLayout的宽高是通过测量子类的宽高,得其最大的宽高在加上padding,在进过一系列的判断才最终得到的</span><br><span class="line"> setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),</span><br><span class="line"> resolveSizeAndState(maxHeight, heightMeasureSpec,</span><br><span class="line"> childState << MEASURED_HEIGHT_STATE_SHIFT));</span><br><span class="line"></span><br><span class="line"> // 9、获取子类中宽或高设置match_parent的个数,如果其个数大于0,我们就需要对这些view重新测量,</span><br><span class="line"> count = mMatchParentChildren.size();</span><br><span class="line"> if (count > 1) {</span><br><span class="line"> for (int i = 0; i < count; i++) {</span><br><span class="line"> final View child = mMatchParentChildren.get(i);</span><br><span class="line"> final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();</span><br><span class="line"></span><br><span class="line"> final int childWidthMeasureSpec;</span><br><span class="line"> if (lp.width == LayoutParams.MATCH_PARENT) {</span><br><span class="line"> final int width = Math.max(0, getMeasuredWidth()</span><br><span class="line"> - getPaddingLeftWithForeground() - getPaddingRightWithForeground()</span><br><span class="line"> - lp.leftMargin - lp.rightMargin);</span><br><span class="line"> childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(</span><br><span class="line"> width, MeasureSpec.EXACTLY);</span><br><span class="line"> } else {</span><br><span class="line"> childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,</span><br><span class="line"> getPaddingLeftWithForeground() + getPaddingRightWithForeground() +</span><br><span class="line"> lp.leftMargin + lp.rightMargin,</span><br><span class="line"> lp.width);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> final int childHeightMeasureSpec;</span><br><span class="line"> if (lp.height == LayoutParams.MATCH_PARENT) {</span><br><span class="line"> final int height = Math.max(0, getMeasuredHeight()</span><br><span class="line"> - getPaddingTopWithForeground() - getPaddingBottomWithForeground()</span><br><span class="line"> - lp.topMargin - lp.bottomMargin);</span><br><span class="line"> childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(</span><br><span class="line"> height, MeasureSpec.EXACTLY);</span><br><span class="line"> } else {</span><br><span class="line"> childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,</span><br><span class="line"> getPaddingTopWithForeground() + getPaddingBottomWithForeground() +</span><br><span class="line"> lp.topMargin + lp.bottomMargin,</span><br><span class="line"> lp.height);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> child.measure(childWidthMeasureSpec, childHeightMeasureSpec);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>看完FrameLayout的测量不知道你们有没有理解清楚,心里是否还有疑问?有人说你说的已经很清楚了没有什么问题的,哪好我在问你们一个问题,子View为什么需要进行二次测量,是这样的当view的宽或者高设置match_parent的时候,子类自己也不知道自己的宽高到底是多少,只知道和父类一样就行了,当父类的宽高确定了之后,就需要对那些设置了match_parent的view重新测量,设置宽高。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>源码分析完了接下来我们来回顾总结一下,测量是从ViewRootImpl中的requestLayout()方法发起,经过一系列的操作最终调到performTraversals(), 在该方法中会依次调用performMeasure()、performLayout()、performDraw()。 在requestLayout()中有一个参数mLayoutRequested很重要,它很大程度上决定了performMeasure()和performLayout()是否调用,对performDraw()没有什么影响,我们先来看performMeasure(), 发现其内部最终调用的是View的measure(),最终调用到我们最熟悉的onMeasure(). 这时我们就分别介绍了View的onMeasure()和ViewGroup的onMeasure()</p>
<ol>
<li><p>View的onMeasure()</p>
<ul>
<li>View的onMeasure()很简单,直接调用setMeasuredDimension(), 设置View的宽高,通过setMeasuredDimensionRaw()保留我们设置的宽高,使用的时候通过getMeasureWidth()、getMeasureHeight()来获取我们设置的宽高</li>
</ul>
</li>
<li><p>ViewGroup的onMeasure()</p>
<ul>
<li>ViewGroup的onMeasure()很复杂,ViewGroup的宽高是通过子类的宽高来决定的,所以需要先测量所有子类的宽高,得到子类宽高的最值,然后通过子类的宽高在加上一些padding值及其他条件的校验最终得出ViewGroup的宽高,在设置ViewGroup的宽高,然后再重新测量子类宽高设置为match_parent的view</li>
</ul>
</li>
</ol>
<p>接下来我们看一张就一目了然了</p>
<p><img src="/Users/apple/Desktop/blog/View%E7%9A%84%E6%B5%8B%E9%87%8F.png" alt="1.png"></p>
<h2 id="requestLayout-、invalidate-、-postInvalidate-的区别"><a href="#requestLayout-、invalidate-、-postInvalidate-的区别" class="headerlink" title="requestLayout()、invalidate()、 postInvalidate()的区别"></a>requestLayout()、invalidate()、 postInvalidate()的区别</h2><p> 其实这个跟本章内容不是很搭,为什么在这里提出来呢,因为他们之间的区别在本文的代码里面就能够很好的区别开来,源码不是很多我就不一一展示了,想弄清楚的同学可以自己翻源码好好看看。</p>
<ul>
<li>requestLayout(): 我们就不用多说了,本文的起点就是从它开始的,通过它才有了后面View的measure、layout和draw。 </li>
<li>invalidate(): 这个方法我们也不陌生,用来更新界面,这时我们就要问了它更新界面的时候还会走measure和layout吗?我想这个你自己也不清楚吧,进入源码我们可以看到View的invalidate()会递归的调用 parent.invalidateChildInParent(),最终调用ViewRootImpl的invalidateChildInParent(), 通过查看源码得知ViewRootImpl的invalidateChildInParent()内部调用的是invalidate(), 注意这个invalidate()是ViewRootImpl中的,而invalidate()内部调用的是scheduleTraversals(),看到这里我们就熟悉了下面的操作了,看到这里的同学就知道了接下来就要执行performTraversals(),在接着执行performMeasure()、performLayout()和performDraw()了,慢着,这里需要暂停一下,还记得我在前面一直说的要你注意mLayoutRequested这个标记吗?当我们requestLayout()之后会将其置为false,只有在调用requestLayout的时候将其置为true。当你调用invalidate()的时候该标记依然为false,就不会走performMeasure()和performLayout(),直接走的是performDraw()。</li>
<li>postInvalidate(): 其内部还是调用的是invalidate(), 只不过它可以在子线程通知更新UIblo</li>
</ul>
<h2 id="实战应用"><a href="#实战应用" class="headerlink" title="实战应用"></a>实战应用</h2><p> 至此我们对View的测量流程已经有了大致的了解了,可能有人就要问了,哪这些对我们实际开发中有什么用呢?实际开发的时候有用到吗?同学别急,这个大有用处,听我为你娓娓道来。在项目中根据需求我们需要自定义view,这里我们给自定义的View分为两种,一种是根据系统已有的view,然后根据需要自定义我们所需要的,另一种是完全的自定义。</p>
<ol>
<li><p>根据系统已有View自定义我们需要的View</p>
<p> 举个例子:我们需要一个方形的ImageView,无论你宽高怎么设置我得到的都是方形的。看到这个需求我们首先想到的是系统给我们提供了Imageview,但系统的满足不了我们的需求,ImageView的宽高究竟是怎么测量的我们不得而知,但是我们可以获取系统帮我们测量的imageview的宽高,然后我们在将它的宽高设置成一样的就行了</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {</span><br><span class="line"> super.onMeasure(widthMeasureSpec, heightMeasureSpec);</span><br><span class="line"> int measuredHeight = getMeasuredHeight();</span><br><span class="line"></span><br><span class="line"> int measuredWidth = getMeasuredWidth();</span><br><span class="line"> int result = 0;</span><br><span class="line"> if (measuredHeight > measuredWidth) {</span><br><span class="line"> result = measuredHeight;</span><br><span class="line"> } else {</span><br><span class="line"> result = measuredWidth;</span><br><span class="line"> }</span><br><span class="line"> setMeasuredDimension(result, result);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
</li>
<li><p>系统没有定义的,完全由我们自定义</p>
<p> 由于是完全的自定义,系统也没法帮我们测量,需要我们自己计算自己所需要的宽高,所以不需要super.onMeasure(), 在父类所限制的测量模式下计算出我们所需要的宽高,最后调用setMeasuredDimension()设置我们的宽高。另一种方式是直接计算我们需要的宽高,计算完了之后调用resolveSize(size, measureSpec)校验我们计算的宽高,其中measureSpec是父类对子类的宽高测量规格。然后在调用setMeasuredDimension()设置宽高。</p>
</li>
</ol>
]]></content>
<tags>
<tag>Android View测量</tag>
</tags>
</entry>
<entry>
<title>Handler通信机制</title>
<url>/2018/10/21/Handler%E9%80%9A%E4%BF%A1%E6%9C%BA%E5%88%B6/</url>
<content><![CDATA[<p>##Handler通信机制<br>Handler主要用在子线程完成耗时的操作后通知主线程来更新UI,因为只有UI线程才能修改UI组件</p>
<p>消息传递有三个部分组成 Looper、 MessageQueue、Handler,Message是要传递的消息</p>
<p>当我们的应用启动的时候主线程调用Looper.prepare(),来初始化一个looper对象和一个MessageQueue对象,并将Looper对象存放在本地线程LocalThread中,当我们下次在使用的时候就会先从LocalThread中获取Looper对象接下来会执行Looper.loop()进入消息循环,遍历MessageQueue中获取消息,当我们的子线程在完成耗时操作后就会发送一个msg到MessageQueue,looper遍历到该消息后就会将其消息队列中取出,如果消息为空则会阻塞,如果不为空就会将消息分派给创建该消息的Handler,Handler就会调用dispatchMessage(msg)方法,即回调HandleMessage()来处理消息</p>
<p>内存泄漏 :本该被回收的对象没有被及时回收停留在堆内存中</p>
<p>原因:当前使用的对象持有本该被回收对象的引用,导致它不能被回收<br>##Handler内存泄漏<br>handler内存泄漏的原因是即 Handler消息队列还有未处理的消息/正在处理消息而外部类需销毁而没能及时销毁导致的</p>
<p>java中非静态内部类和匿名内部类都会潜在引用他们所属的外部类,静态内部类则没有</p>
<p><strong>解决Handler内存泄漏有两种方法</strong></p>
<ol>
<li><p>使用静态的内部类,用弱引用来持有当前Activity的实例</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// 定义 弱引用实例</span><br><span class="line"> private WeakReference<Activity> reference;</span><br><span class="line"> // 在构造方法中传入需持有的Activity实例</span><br><span class="line"> public FHandler(Activity activity) {</span><br><span class="line"> // 使用WeakReference弱引用持有Activity实例</span><br><span class="line"> reference = new WeakReference<Activity>(activity); </span><br><span class="line"> }</span><br></pre></td></tr></table></figure></li>
<li><p>在activity销毁的时候清除消息队列中的所有消息handler.removeCallbacksAndMessages(null);</p>
</li>
</ol>
]]></content>
</entry>
<entry>
<title>事件分发</title>
<url>/2018/10/21/%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91/</url>
<content><![CDATA[<p>##事件分发<br>Android中事件分发顺序:Activity(Window) -> ViewGroup -> View<br>事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成</p>
<p>###<strong>特别注意:</strong><br>如果ViewGroup A 从DOWN事件开始拦截,那么后续事件也会由如果ViewGroup A来处理 onInterceptTouchEvent()方法一旦返回true就不会再调用</p>
<p>如果ViewGroup A 拦截了一个半路的事件(如MOVE),这个事件将会被系统变成一个CANCEL事件并传递给之前处理该事件的子View;<br>该事件不会再传递给ViewGroup A的onTouchEvent()<br>只有再到来的事件才会传递到ViewGroup A的onTouchEvent()</p>
<p>当一个点击事件发生时,调用顺序如下</p>
<p>事件最先传到Activity的dispatchTouchEvent()进行事件分发<br>调用Window类实现类PhoneWindow的superDispatchTouchEvent()<br>调用DecorView的superDispatchTouchEvent()<br>最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()</p>
<p>如果ViewGroup的dispatchTouchEvent()返回true就不执行Activity的onTouchEvent()方法;如果返回false,就执行。</p>
<p>##1.ViewGroup的事件分发</p>
<p>Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View<br>在ViewGroup中通过onInterceptTouchEvent()对事件传递进行拦截<br>onInterceptTouchEvent方法返回true代表拦截事件,即不允许事件继续向子View传递;<br>返回false代表不拦截事件,即允许事件继续向子View传递;(默认返回false)<br>子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。</p>
<p>##2.View事件的分发机制<br>View中dispatchTouchEvent()的源码分析</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public boolean dispatchTouchEvent(MotionEvent event) { </span><br><span class="line"> if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && </span><br><span class="line"> mOnTouchListener.onTouch(this, event)) { </span><br><span class="line"> return true; </span><br><span class="line"> } </span><br><span class="line"> return onTouchEvent(event); </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>onTouch()的执行高于onClick()</p>
<p>每当控件被点击时:<br>如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。<br>onTouch()返回false(该事件没被onTouch()消费掉) = dispatchTouchEvent()返回false(继续向下传递) = 执行onTouchEvent() = 执行OnClick()<br>如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;<br>onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()</p>
]]></content>
</entry>
<entry>
<title>MacDown语法</title>
<url>/2018/10/20/MacDown%E8%AF%AD%E6%B3%95/</url>
<content><![CDATA[<p>##<a href="https://blog.csdn.net/witnessai1/article/details/52551362?utm_source=blogxgwz0" target="_blank" rel="noopener">MacDown语法</a></p>
<p>##快捷键<br>效果|语法|快捷键|<br>:–|:–:|:–:|<br>粗体|<strong>我是粗体</strong>|cmd + k<br>斜体|<em>我是斜体</em>|cmd + i<br>删除|<del>删除</del>|<br>单行代码|``|cmd + k<br>超链接|<a href="">我是超链接</a>|cmd + shift + k </p>
<p>##制表<br>|居左 |居中|居右|<br>|:–|:—–:| ———:|<br>|我是左边|我是中间|我是右边|</p>
]]></content>
</entry>
<entry>
<title>多线程基础</title>
<url>/2018/10/20/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%9F%BA%E7%A1%80/</url>
<content><![CDATA[<p>#多线程<br>##线程的创建</p>
<ol>
<li><p>继承Thread类</p>
<pre><code>new Thread().start();</code></pre></li>
<li><p>实现Runnable接口(JDK1.0)</p>
<pre><code>new Thread(myRunnable).start();</code></pre></li>
<li><p>通过CallAble和FutureTask(JDK1.5)</p>
<pre><code>public class MyCallAble implements CallAble<T> {
public T call() {
return T;
}
MyCallAble<T> myCallable = new MyCallAble<T>();
FutureTask futureTask = new FutrueTask(myCallable);
T t = futureTask.get();
new Thread(futureTask).start();</code></pre></li>
</ol>
<p>##三种创建线程的优缺点<br> 创建线程的三种方法 | 返回值 |异常|阻塞|资源共享|<br> :——-:|:—–:|:–:|:–:|:—:|<br> 继承Thread类 | 无 | 无|没有|不行|<br> 实现Runnable接口 | 无 | 无|没有|可以|<br> 通过CallAble和FutureTask |有|有|有|可以|</p>
<p>继承Thread类来创建线程,当创建多个线程时,多个线程之间是不能进行资源共享的</p>
<p>实现Runnable接口来创建的线程,创建线程是可以使用同一个Runnable实例,那么线程之间就能资源共享</p>
<p>实现CallAble接口,在创建一个FutureTask实例来创建的线程,通过该接口创建的线程会有一个返回值,通过futureTask.get()获得,而且还可以抛异常,而Runnable接口则没有返回值和异常,<strong>当执行futureTask实例创建的线程时其他线程就会阻塞,直到该线程执行完毕才会执行其他的</strong></p>
<h2 id="Sleep-和Wait-的区别"><a href="#Sleep-和Wait-的区别" class="headerlink" title="Sleep()和Wait()的区别"></a>Sleep()和Wait()的区别</h2><p>都是停止该线程的调用,Sleep()是线程内的静态方法,wait()是Object的方法,sleep()调用时不会释放锁,wait()调用时会释放锁,Sleep()调用的线程时间到了就会到就绪状态而wait()调用的线程需要调用notify(),当它再次获取到锁时才会进入就绪状态</p>
<h2 id="Sleep-和Yield-的区别"><a href="#Sleep-和Yield-的区别" class="headerlink" title="Sleep()和Yield()的区别"></a>Sleep()和Yield()的区别</h2><p>调用yield()会让当前线程交出CPU权限,让CPU去执行其他的线程,调用sleep()不会考虑优先级,会给其他低优先级的线程执行的机会,而调用yield()方法只会给与它优先级相同或者高于它优先级的线程,线程执行sleep()会进入阻塞状态而调用yield()的线程会进入就绪状态,sleep()会抛出InterruptedException异常而yield()没有异常抛出</p>
<p><a href="https://www.jianshu.com/p/8a04b5ec786c" target="_blank" rel="noopener">多线程基础</a><br>##<a href="https://www.jianshu.com/p/6ec47fdb0ab1" target="_blank" rel="noopener">线程同步</a></p>
<ul>
<li><p>使用Synchronized</p>
<p> synchronized关键字作用在静态方法上是给Class类上锁,而作用在非static<br> 静态方法上是给对象上锁</p>
</li>
<li><p>Volatile关键字</p>
<p> Volatile是使变量在多个线程间可见,它是强制从公共堆栈中取得变量的值而不是从私有数据堆栈中取得变量的值,<strong>只能修饰变量不能修饰方法和代码块</strong>,不具有原子性,多线程访问Volatile不会发生阻塞</p>
</li>
<li><p>使用Lock(JDK1.5)</p>
<p> 占有锁的线程释放锁一般会是以下三种情况之一:</p>
<ol>
<li><p>占有锁的线程执行完了该方法或代码块,然后释放对锁的占有;</p>
</li>
<li><p>占有锁线程执行发生异常,此时JVM会让线程自动释放锁;</p>
</li>
<li><p>占有锁线程进入 WAITING 状态从而释放锁,例如在该线程中调用wait()方法等</p>
</li>
</ol>
<p>以下几种情况下需考虑使用Lock</p>
<ol>
<li>能够响应中断</li>
<li>多线程读情况, 使用ReentrantReadWriteLock</li>
<li>判断线程是否成功获得锁</li>
</ol>
<p> <strong>使用Lock需要注意</strong>:</p>
<blockquote>
<p>当我们使用synchronized时是不需要用户去手动释放锁,当synchronized方法或者代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock则必须要用户去手动释放锁 (发生异常时,不会自动释放锁),如果没有主动释放锁,就有可能导致死锁现象</p>
</blockquote>
</li>
</ul>
<p>###Synchronized和Lock的区别</p>
<ul>
<li>Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;</li>
<li>synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。Lock在发生异常时,如果没有主动通过unLock()方法去释放锁,则很可能造成死锁的现象,因此使用Lock时需要在finally块中释放锁;</li>
<li>Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;</li>
<li>通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;</li>
<li>Lock可以提高多个线程进行读操作的效率。</li>
</ul>
]]></content>
</entry>
<entry>
<title>一行代码解决TextView 不等字数两端对齐问题</title>
<url>/2017/11/01/Textview%E4%B8%A4%E7%AB%AF%E5%AF%B9%E9%BD%90/</url>
<content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>先附上一张图<br><img src="/Users/liuxianjun/Documents/tmp25e6a1a7.png" alt="图片"><br> 上面的图片相信大家都很熟悉,项目中经常会遇到这种需求,我看到它的第一感觉这不就是一个TextView嘛?很简单啊!可当我写布局的时候怎么做到文字两端对齐,这可就难倒我了,我首先想到Google应该给TextView设置了这样的属性,我找了一下然而并没有,要么就自己写一个控件来实现这个效果,然而怎么写就犯难了,后来我想到SpannableStringBuilder可以给TextView设置很多不同的效果,给SpannableStringBuilder设置不同的Span就可以达到我们想要的效果,经过一番努力后终于找到了,言归正传我们开始今天的话题。</p>
<h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">tv1.setText(AlignedTextUtil.justifyString(<span class="string">"手机号:"</span>, <span class="number">6</span>);</span><br><span class="line">tv2.setText(AlignedTextUtil.justifyString(<span class="string">"密码:"</span>, <span class="number">6</span>);</span><br><span class="line">tv3.setText(AlignedTextUtil.justifyString(<span class="string">"确认密码:"</span>, <span class="number">6</span>);</span><br></pre></td></tr></table></figure>
<p>怎么样,是不是很简单,只需要一行代码就可以轻松搞定TextView两端对齐的问题。</p>
<p><strong>注意这里的标点符号或者数字必须是全角模式下的符号或数字,因为全角模式下汉字,数字,符号等所占比例是一致的</strong></p>
<h2 id="关键代码"><a href="#关键代码" class="headerlink" title="关键代码"></a>关键代码</h2><p>直接上AlignedTextUtil类的代码</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AlignedTextUtil</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 设置两端对齐的textview的格式</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> str</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> size size必须要大于或者等于两端对齐文字中最长的那一个</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SpannableStringBuilder <span class="title">justifyString</span><span class="params">(String str, <span class="keyword">int</span> size)</span> </span>{</span><br><span class="line"> SpannableStringBuilder spannableStringBuilder = <span class="keyword">new</span> SpannableStringBuilder();</span><br><span class="line"> <span class="keyword">if</span> (TextUtils.isEmpty(str)) {</span><br><span class="line"> <span class="keyword">return</span> spannableStringBuilder;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">char</span>[] chars = str.toCharArray();</span><br><span class="line"> <span class="keyword">if</span> (chars.length >= size || chars.length == <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> spannableStringBuilder.append(str);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">int</span> l = chars.length;</span><br><span class="line"> <span class="keyword">float</span> scale = (<span class="keyword">float</span>) (size - l) / (l - <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < l; i++) {</span><br><span class="line"> spannableStringBuilder.append(chars[i]);</span><br><span class="line"> <span class="keyword">if</span> (i != l - <span class="number">1</span>) {</span><br><span class="line"> SpannableString s = <span class="keyword">new</span> SpannableString(<span class="string">" "</span>);<span class="comment">//全角空格</span></span><br><span class="line"> s.setSpan(<span class="keyword">new</span> ScaleXSpan(scale), <span class="number">0</span>, <span class="number">1</span>, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);</span><br><span class="line"> spannableStringBuilder.append(s);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> spannableStringBuilder;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>只有短短的20行代码,是不是很简单,接下来我们就来分析一下.</p>
<h2 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h2><p> 当TextView的Size确定之后,字的宽度也就确定了,在全角状态下子汉子和符号的宽度是一样的,这里我们假设其宽度 <strong>W = 1</strong>, 字的个数为<strong>TN</strong>,那么字的宽度<strong>TW = W * TN</strong>,要使TextView两端对齐,总宽度<strong>Length</strong>也是确定的,那么字与字之间总的空隙长度<br> <strong>S = Length - TW</strong>。空隙的个数<strong>N = TN - 1</strong>, 那么每个空隙的宽度<strong>SW = S / N</strong>, 接下来我们在每个字之间插入空格,将空格的宽度缩放为<strong>SW</strong>的宽度即可。</p>
<p><img src="/Users/liuxianjun/Documents/%E6%89%8B%E6%9C%BA%E5%8F%B7.png" alt="手机号"></p>
<p> 获取缩放的比例</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">float</span> scale = (<span class="keyword">float</span>) (size - l) / (l - <span class="number">1</span>);</span><br></pre></td></tr></table></figure>
<p>遍历原始字符,将空格插入其中</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < l; i++) {</span><br><span class="line"> spannableStringBuilder.append(chars[i]);</span><br><span class="line"> <span class="keyword">if</span> (i != l - <span class="number">1</span>) {</span><br><span class="line"> SpannableString s = <span class="keyword">new</span> SpannableString(<span class="string">" "</span>);<span class="comment">//全角空格</span></span><br><span class="line"> s.setSpan(<span class="keyword">new</span> ScaleXSpan(scale), <span class="number">0</span>, <span class="number">1</span>, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);</span><br><span class="line"> spannableStringBuilder.append(s);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="http://blog.csdn.net/qq_16430735/article/details/50427978" target="_blank" rel="noopener">Android中各种Span的用法</a></p>
]]></content>
<tags>
<tag>android textview 两端对齐</tag>
</tags>
</entry>
<entry>
<title>简述热修复原理</title>
<url>/2017/11/01/%E7%83%AD%E4%BF%AE%E5%A4%8D%E5%8E%9F%E7%90%86/</url>
<content><![CDATA[<h2 id="热修复原理"><a href="#热修复原理" class="headerlink" title="热修复原理"></a>热修复原理</h2><p>Android中有两个主要的Classloader,PathClassLoader和DexClassLoader,它们都继承自BaseDexClassLoader,这两个类加载器的主要区别是:Android系统通过PathClassLoader来加载系统类和主dex中的类。而DexClassLoader则可用于加载指定路径的apk、jar或dex文件。上述两个类都是继承自BaseDexClassLoader<br>BaseDexClassLoader 的构造函数中创建一个DexPathList实例,DexPathList的构造函数会创建一个dexElements 数组,BaseDexClassLoader 在findclass方法中调用了pathList.findClass,这个方法中会遍历dexpathlist中的dexElements数组,然后初始化DexFile,如果DexFile不为空那么调用DexFile类loadClassBinaryName方法返回Class实例。简言之,ClassLoader会遍历dexelements,然后加载这个数组中的dex文件. ClassLoader在加载到正确的类之后就会停止加载此类,因此我们将包含正确的类的Dex文件中插入在dexElements数组前面就可以完成对问题类的修复</p>
<h3 id="diff算法的主要流程"><a href="#diff算法的主要流程" class="headerlink" title="diff算法的主要流程"></a>diff算法的主要流程</h3><p> 对于dex文件中的每项Section,遍历其每一项Item,进行新数据与旧数据的对比(新旧数据的对比方法是oldItem.compareTo(newItem),结果小于0记为DEL,大于0记为ADD),记录在哪些位置需要ADD一个值,哪些位置需要DEL一个值,并把这些操作项存放于patchOperationList中。按照图中的例子,就是删除位置2的数据,在位置5添加f。为了减少存储的数据,Tinker中会遍历patchOperationList,将同一个位置既有DEL标识又有ADD标识的情况,替换为REPLACE标识,最后将ADD,DEL,REPLACE标识数据分别记录到各自的List中最后将操作记录列表写入补丁patch.dex中生成patch.dex后,进行下一步是将patch下发到客户端后合成全量的dex,合成dex这部分内容此处不再展开说,是差量过程的反过程</p>
<p><strong>Tinker进行热修复的流程为:</strong></p>
<ol>
<li>新dex与旧dex通过dex差分算法生成差异包 patch.dex</li>
<li>将patch dex下发到客户端,客户端将patch dex与旧dex合成为新的全量dex</li>
<li>将合成后的全量dex 插入到dex elements前面(此部分和QQ空间机制类似),完成修复</li>
</ol>
]]></content>
<tags>
<tag>android tinker 热修复</tag>
</tags>
</entry>
</search>