DevBytes: RequestDuringLayout

簡介:

Gooogle Android 團隊在  https://www.youtube.com/watch?v=HbAeTGoKG6k 的課程《DevBytes: RequestDuringLayout》,描述了,我們不應該在代碼佈局的時候,我們不應該在上面寫一些code,特別是佈局的代碼,這將要影響佈局。

Demo下載地址:http://developer.android.com/shareables/devbytes/RequestDuringLayout.zip

Code:

**
 * This example shows what horrible things can result from calling requestLayout() during
 * a layout pass. DON'T DO THIS.
 * This example shows 一個糟糕的結果,當調用了 requestLayout() 在佈局的過程中。不要這樣做!
 *
 * Watch the associated video for this demo on the DevBytes channel of developer.android.com
 * or on YouTube at https://www.youtube.com/watch?v=HbAeTGoKG6k.
 */
public class RequestDuringLayout extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_request_during_layout);

        final MyLayout myLayout = (MyLayout) findViewById(R.id.container);
        Button addViewButton = (Button) findViewById(R.id.addView);
        Button removeViewButton = (Button) findViewById(R.id.removeView);
        Button forceLayoutButton = (Button) findViewById(R.id.forceLayout);

        addViewButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myLayout.mAddRequestPending = true;
                myLayout.requestLayout();
            }
        });

        removeViewButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myLayout.mRemoveRequestPending = true;
                myLayout.requestLayout();
            }
        });

        forceLayoutButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myLayout.requestLayout();
            }
        });

    }

    /**
     * Custom layout to enable the convoluted way of requesting-during-layout that we're
     * trying to show here. Yes, it's a hack. But it's a case that many apps hit (in much more
     * complicated and less demoable ways), so it's interesting to at least understand the
     * artifacts that come from this sequence of events.
     * 
     * 
     */
    static class MyLayout extends LinearLayout {

        int numButtons = 0;
        boolean mAddRequestPending = false;
        boolean mRemoveRequestPending = false;

        public MyLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

        public MyLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public MyLayout(Context context) {
            super(context);
        }

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
        	super.onLayout(changed, l, t, r, b);
            // Here is the root of the problem: we are adding/removing views during layout. This
            // means that this view and its container will be put into an uncertain state that
            // can be difficult to discover and recover from.
        	// Better approach: just add/remove at a time when layout is not running, certainly not
            // in the middle of onLayout(), or other layout-associated logic.
//      >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>	
//       	 Here is the root of the problem:we adding/removing views during layout這意味着,view和
//       	它的container(容器)將要存在一個不安全的狀態,這將是很難發現和修復的。
//        	Better approach: just add/remove 在一個佈局不是在運行的時候,也不是在佈局的任何的邏輯順序的時候。
        	
            if (mRemoveRequestPending) {
                removeButton();
                mRemoveRequestPending = false;
            }
            if (mAddRequestPending) {
                addButton();
                mAddRequestPending = false;
            }
            

        }

        private void removeButton() {
            if (getChildCount() > 1) {
                removeViewAt(1);
            }
        }

        private void addButton() {
            Button button = new Button(getContext());
            button.setLayoutParams(new LayoutParams(
                    LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            button.setText("Button " + (numButtons++));
            addView(button);
        }

    }

}


<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<view xmlns:android="http://schemas.android.com/apk/res/android"
      class="com.android.requestduringlayout.RequestDuringLayout$MyLayout"
      android:orientation="vertical"
      android:id="@+id/container"
      android:layout_width="match_parent"
      android:layout_height="match_parent" >

    <LinearLayout
            android:orientation="horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
        <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/addView"
                android:id="@+id/addView" />
        <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/removeView"
                android:id="@+id/removeView" />
        <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/forceLayout"
                android:id="@+id/forceLayout" />
    </LinearLayout>

</view>

截圖:

      

當點擊了Add button的時候,沒有效果,在點擊Layout Button的時候,纔會出現圖二的效果。

在容器進行佈局的時候會onMeasure  onLayout ...之類的過程.

我們不應該在圖形佈局的時候的任何的邏輯的過程中,進行一些影響佈局的操作。

view繪製過程:

Category Methods Description
Creation Constructors There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.
onFinishInflate() Called after a view and all of its children has been inflated from XML.
Layout onMeasure(int, int) Called to determine the size requirements for this view and all of its children.
onLayout(boolean, int, int, int, int) Called when this view should assign a size and position to all of its children.
onSizeChanged(int, int, int, int) Called when the size of this view has changed.
Drawing onDraw(android.graphics.Canvas) Called when the view should render its content.
Event processing onKeyDown(int, KeyEvent) Called when a new hardware key event occurs.
onKeyUp(int, KeyEvent) Called when a hardware key up event occurs.
onTrackballEvent(MotionEvent) Called when a trackball motion event occurs.
onTouchEvent(MotionEvent) Called when a touch screen motion event occurs.
Focus onFocusChanged(boolean, int, android.graphics.Rect) Called when the view gains or loses focus.
onWindowFocusChanged(boolean) Called when the window containing the view gains or loses focus.
Attaching onAttachedToWindow() Called when the view is attached to a window.
onDetachedFromWindow() Called when the view is detached from its window.
onWindowVisibilityChanged(int) Called when the visibility of the window containing the view has c
ps:view的繪製的流程 https://developer.android.com/intl/zh-cn/reference/android/view/View.html

解決方案:

解決這個問題有很多種方式,最簡單的方式就是註釋掉onLayout中的Code,直接調用addButton方法即可。

見code:

public class RequestDuringLayout extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_request_during_layout);

        final MyLayout myLayout = (MyLayout) findViewById(R.id.container);
        Button addViewButton = (Button) findViewById(R.id.addView);
        Button removeViewButton = (Button) findViewById(R.id.removeView);
        Button forceLayoutButton = (Button) findViewById(R.id.forceLayout);

        addViewButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                myLayout.mAddRequestPending = true;
//                myLayout.requestLayout();
            	myLayout.addButton();
            }
            
        });

        removeViewButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                myLayout.mRemoveRequestPending = true;
//                myLayout.requestLayout();
            	myLayout.removeButton();
            }
        });

        forceLayoutButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myLayout.requestLayout();
            }
        });

    }

    static class MyLayout extends LinearLayout {

        int numButtons = 0;
        boolean mAddRequestPending = false;
        boolean mRemoveRequestPending = false;

        public MyLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

        public MyLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public MyLayout(Context context) {
            super(context);
        }

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
        	super.onLayout(changed, l, t, r, b);
        }

        private void removeButton() {
            if (getChildCount() > 1) {
                removeViewAt(1);
            }
        }

        private void addButton() {
            Button button = new Button(getContext());
            button.setLayoutParams(new LayoutParams(
                    LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            button.setText("Button " + (numButtons++));
            addView(button);
        }

    }

}

參考:



發佈了120 篇原創文章 · 獲贊 1 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章