Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 自定義布局 性能問題 初探

Android 自定義布局 性能問題 初探

編輯:關於Android編程

大家在寫android 代碼的時候,基本上都使用過如下幾種布局 RelativeLayout,LinearLayout, FrameLayout   但是很多時候 這幾種布局 也無法滿足我們的使用。於是我們會考慮用自定義布局,使用自定義布局會有幾個優點   比如可以減少view的使用啊,讓ui顯示的更加有效率啊,以及實現一些原生控件無法實現的效果。       我們首先去github上 下載一個開源項目 https://github.com/lucasr/android-layout-samples   注意這個項目是基於android studio 結構的。你如果用Eclipse來導入是導入不成功的。   最近github上很多開源項目都開始支持android studio了。所以還是建議大家擁抱下谷歌的新ide。       然後這個項目的作者是http://lucasr.org/about/ 就是國外一個很牛逼的android 工程師,我們就以他   的開源項目以及博客 來感受一下 自定義布局的性能。       這個項目運行起來以後實際上就是仿照的twitter的一些效果。圖片庫用的是picasso。有興趣的同學可以   去www.2cto.com這個地方看一下這個圖片庫。   然後我們來看第一個自定義ui  TweetCompositeView    1 /*  2  * Copyright (C) 2014 Lucas Rocha  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *     http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16  17 package org.lucasr.layoutsamples.widget; 18  19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.util.AttributeSet; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.widget.ImageView; 25 import android.widget.RelativeLayout; 26 import android.widget.TextView; 27  28 import org.lucasr.layoutsamples.adapter.Tweet; 29 import org.lucasr.layoutsamples.adapter.TweetPresenter; 30 import org.lucasr.layoutsamples.app.R; 31 import org.lucasr.layoutsamples.util.ImageUtils; 32  33 import java.util.EnumMap; 34 import java.util.EnumSet; 35  36 public class TweetCompositeView extends RelativeLayout implements TweetPresenter { 37     private final ImageView mProfileImage; 38     private final TextView mAuthorText; 39     private final TextView mMessageText; 40     private final ImageView mPostImage; 41     private final EnumMap<Action, ImageView> mActionIcons; 42  43     public TweetCompositeView(Context context, AttributeSet attrs) { 44         this(context, attrs, 0); 45     } 46  47     public TweetCompositeView(Context context, AttributeSet attrs, int defStyleAttr) { 48         super(context, attrs, defStyleAttr); 49  50         LayoutInflater.from(context).inflate(R.layout.tweet_composite_view, this, true); 51         mProfileImage = (ImageView) findViewById(R.id.profile_image); 52         mAuthorText = (TextView) findViewById(R.id.author_text); 53         mMessageText = (TextView) findViewById(R.id.message_text); 54         mPostImage = (ImageView) findViewById(R.id.post_image); 55  56         mActionIcons = new EnumMap(Action.class); 57         for (Action action : Action.values()) { 58             final ImageView icon; 59             switch (action) { 60                 case REPLY: 61                     icon = (ImageView) findViewById(R.id.reply_action); 62                     break; 63  64                 case RETWEET: 65                     icon = (ImageView) findViewById(R.id.retweet_action); 66                     break; 67  68                 case FAVOURITE: 69                     icon = (ImageView) findViewById(R.id.favourite_action); 70                     break; 71  72                 default: 73                     throw new IllegalArgumentException("Unrecognized tweet action"); 74             } 75  76             mActionIcons.put(action, icon); 77         } 78     } 79  80     @Override 81     public boolean shouldDelayChildPressedState() { 82         return false; 83     } 84  85     @Override 86     public void update(Tweet tweet, EnumSet<UpdateFlags> flags) { 87         mAuthorText.setText(tweet.getAuthorName()); 88         mMessageText.setText(tweet.getMessage()); 89  90         final Context context = getContext(); 91         ImageUtils.loadImage(context, mProfileImage, tweet.getProfileImageUrl(), flags); 92  93         final boolean hasPostImage = !TextUtils.isEmpty(tweet.getPostImageUrl()); 94         mPostImage.setVisibility(hasPostImage ? View.VISIBLE : View.GONE); 95         if (hasPostImage) { 96             ImageUtils.loadImage(context, mPostImage, tweet.getPostImageUrl(), flags); 97         } 98     } 99 } 復制代碼     我們可以看一下這個自定義ui。實際上這個自定義ui非常簡單,我們工作中也經常這樣使用自定義ui。   他一般就是這麼使用的:   1 繼承一個layout。當然這個layout可以是相對布局 也可以是流布局    2 在構造函數裡 inflate 我們的布局文件 同時初始化我們的自定義布局的子元素   3 增加一些對應的方法 來更新我們的元素 比如說 update 這個方法 就是來做這個工作的。       然後我們來看一下這個布局對應的布局文件       復制代碼  1 <?xml version="1.0" encoding="utf-8"?>  2 <!--  3   ~ Copyright (C) 2014 Lucas Rocha  4   ~  5   ~ Licensed under the Apache License, Version 2.0 (the "License");  6   ~ you may not use this file except in compliance with the License.  7   ~ You may obtain a copy of the License at  8   ~  9   ~     http://www.apache.org/licenses/LICENSE-2.0 10   ~ 11   ~ Unless required by applicable law or agreed to in writing, software 12   ~ distributed under the License is distributed on an "AS IS" BASIS, 13   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14   ~ See the License for the specific language governing permissions and 15   ~ limitations under the License. 16   --> 17  18 <merge xmlns:android="http://schemas.android.com/apk/res/android" 19     android:layout_width="match_parent" 20     android:layout_height="match_parent"> 21  22     <ImageView 23         android:id="@+id/profile_image" 24         android:layout_width="@dimen/tweet_profile_image_size" 25         android:layout_height="@dimen/tweet_profile_image_size" 26         android:layout_marginRight="@dimen/tweet_content_margin" 27         android:scaleType="centerCrop"/> 28  29     <TextView 30         android:id="@+id/author_text" 31         android:layout_width="fill_parent" 32         android:layout_height="wrap_content" 33         android:layout_toRightOf="@id/profile_image" 34         android:layout_alignTop="@id/profile_image" 35         android:textColor="@color/tweet_author_text_color" 36         android:textSize="@dimen/tweet_author_text_size" 37         android:singleLine="true"/> 38  39     <TextView 40         android:id="@+id/message_text" 41         android:layout_width="fill_parent" 42         android:layout_height="wrap_content" 43         android:layout_below="@id/author_text" 44         android:layout_alignLeft="@id/author_text" 45         android:textColor="@color/tweet_message_text_color" 46         android:textSize="@dimen/tweet_message_text_size"/> 47  48     <ImageView 49         android:id="@+id/post_image" 50         android:layout_width="fill_parent" 51         android:layout_height="@dimen/tweet_post_image_height" 52         android:layout_below="@id/message_text" 53         android:layout_alignLeft="@id/message_text" 54         android:layout_marginTop="@dimen/tweet_content_margin" 55         android:scaleType="centerCrop"/> 56  57     <LinearLayout android:layout_width="fill_parent" 58         android:layout_height="wrap_content" 59         android:layout_below="@id/post_image" 60         android:layout_alignLeft="@id/message_text" 61         android:layout_marginTop="@dimen/tweet_content_margin" 62         android:orientation="horizontal"> 63  64         <ImageView 65             android:id="@+id/reply_action" 66             android:layout_width="0dp" 67             android:layout_height="@dimen/tweet_icon_image_size" 68             android:layout_weight="1" 69             android:src="@drawable/tweet_reply" 70             android:scaleType="fitStart"/> 71  72         <ImageView 73             android:id="@+id/retweet_action" 74             android:layout_width="0dp" 75             android:layout_height="@dimen/tweet_icon_image_size" 76             android:layout_weight="1" 77             android:src="@drawable/tweet_retweet" 78             android:scaleType="fitStart"/> 79  80         <ImageView 81             android:id="@+id/favourite_action" 82             android:layout_width="0dp" 83             android:layout_height="@dimen/tweet_icon_image_size" 84             android:layout_weight="1" 85             android:src="@drawable/tweet_favourite" 86             android:scaleType="fitStart"/> 87  88     </LinearLayout> 89  90 </merge> 復制代碼     我們可以來看一下這個布局 其中包含了 LinearLayout 這個布局。 我們知道在android裡面 linearlayout和relativelayout 是非常復雜的ui   這種viewgroup 會不斷的檢測子view的大小和布局位置。 所以實際上效率是有損失的。所以我們如果想更近一步的 優化我們的ui效率   我們要盡量避免使用這種高級的viewgroup    比如我們可以來看看這個view   復制代碼   1 /*   2  * Copyright (C) 2014 Lucas Rocha   3  *   4  * Licensed under the Apache License, Version 2.0 (the "License");   5  * you may not use this file except in compliance with the License.   6  * You may obtain a copy of the License at   7  *   8  *     http://www.apache.org/licenses/LICENSE-2.0   9  *  10  * Unless required by applicable law or agreed to in writing, software  11  * distributed under the License is distributed on an "AS IS" BASIS,  12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  13  * See the License for the specific language governing permissions and  14  * limitations under the License.  15  */  16   17 package org.lucasr.layoutsamples.widget;  18   19 import android.content.Context;  20 import android.text.TextUtils;  21 import android.util.AttributeSet;  22 import android.view.LayoutInflater;  23 import android.view.View;  24 import android.view.ViewGroup;  25 import android.widget.ImageView;  26 import android.widget.TextView;  27   28 import org.lucasr.layoutsamples.adapter.Tweet;  29 import org.lucasr.layoutsamples.adapter.TweetPresenter;  30 import org.lucasr.layoutsamples.app.R;  31 import org.lucasr.layoutsamples.util.ImageUtils;  32   33 import java.util.EnumMap;  34 import java.util.EnumSet;  35   36 public class TweetLayoutView extends ViewGroup implements TweetPresenter {  37     private final ImageView mProfileImage;  38     private final TextView mAuthorText;  39     private final TextView mMessageText;  40     private final ImageView mPostImage;  41     private final EnumMap<Action, View> mActionIcons;  42   43     public TweetLayoutView(Context context, AttributeSet attrs) {  44         this(context, attrs, 0);  45     }  46   47     public TweetLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {  48         super(context, attrs, defStyleAttr);  49   50         LayoutInflater.from(context).inflate(R.layout.tweet_layout_view, this, true);  51         mProfileImage = (ImageView) findViewById(R.id.profile_image);  52         mAuthorText = (TextView) findViewById(R.id.author_text);  53         mMessageText = (TextView) findViewById(R.id.message_text);  54         mPostImage = (ImageView) findViewById(R.id.post_image);  55   56         mActionIcons = new EnumMap(Action.class);  57         for (Action action : Action.values()) {  58             final int viewId;  59             switch (action) {  60                 case REPLY:  61                     viewId = R.id.reply_action;  62                     break;  63   64                 case RETWEET:  65                     viewId = R.id.retweet_action;  66                     break;  67   68                 case FAVOURITE:  69                     viewId = R.id.favourite_action;  70                     break;  71   72                 default:  73                     throw new IllegalArgumentException("Unrecognized tweet action");  74             }  75   76             mActionIcons.put(action, findViewById(viewId));  77         }  78     }  79   80     private void layoutView(View view, int left, int top, int width, int height) {  81         MarginLayoutParams margins = (MarginLayoutParams) view.getLayoutParams();  82         final int leftWithMargins = left + margins.leftMargin;  83         final int topWithMargins = top + margins.topMargin;  84   85         view.layout(leftWithMargins, topWithMargins,  86                     leftWithMargins + width, topWithMargins + height);  87     }  88   89     private int getWidthWithMargins(View child) {  90         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();  91         return child.getWidth() + lp.leftMargin + lp.rightMargin;  92     }  93   94     private int getHeightWithMargins(View child) {  95         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();  96         return child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;  97     }  98   99     private int getMeasuredWidthWithMargins(View child) { 100         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 101         return child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; 102     } 103  104     private int getMeasuredHeightWithMargins(View child) { 105         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 106         return child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; 107     } 108  109     @Override 110     public boolean shouldDelayChildPressedState() { 111         return false; 112     } 113  114     @Override 115     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 116         final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 117  118         int widthUsed = 0; 119         int heightUsed = 0; 120  121         measureChildWithMargins(mProfileImage, 122                                 widthMeasureSpec, widthUsed, 123                                 heightMeasureSpec, heightUsed); 124         widthUsed += getMeasuredWidthWithMargins(mProfileImage); 125  126         measureChildWithMargins(mAuthorText, 127                                 widthMeasureSpec, widthUsed, 128                                 heightMeasureSpec, heightUsed); 129         heightUsed += getMeasuredHeightWithMargins(mAuthorText); 130  131         measureChildWithMargins(mMessageText, 132                                 widthMeasureSpec, widthUsed, 133                                 heightMeasureSpec, heightUsed); 134         heightUsed += getMeasuredHeightWithMargins(mMessageText); 135  136         if (mPostImage.getVisibility() != View.GONE) { 137             measureChildWithMargins(mPostImage, 138                                     widthMeasureSpec, widthUsed, 139                                     heightMeasureSpec, heightUsed); 140             heightUsed += getMeasuredHeightWithMargins(mPostImage); 141         } 142  143         int maxIconHeight = 0; 144         for (Action action : Action.values()) { 145             final View iconView = mActionIcons.get(action); 146             measureChildWithMargins(iconView, 147                                     widthMeasureSpec, widthUsed, 148                                     heightMeasureSpec, heightUsed); 149  150             final int height = getMeasuredHeightWithMargins(iconView); 151             if (height > maxIconHeight) { 152                 maxIconHeight = height; 153             } 154  155             widthUsed += getMeasuredWidthWithMargins(iconView); 156         } 157         heightUsed += maxIconHeight; 158  159         int heightSize = heightUsed + getPaddingTop() + getPaddingBottom(); 160         setMeasuredDimension(widthSize, heightSize); 161     } 162  163     @Override 164     protected void onLayout(boolean changed, int l, int t, int r, int b) { 165         final int paddingLeft = getPaddingLeft(); 166         final int paddingTop = getPaddingTop(); 167  168         int currentTop = paddingTop; 169  170         layoutView(mProfileImage, paddingLeft, currentTop, 171                    mProfileImage.getMeasuredWidth(), 172                    mProfileImage.getMeasuredHeight()); 173  174         final int contentLeft = getWidthWithMargins(mProfileImage) + paddingLeft; 175         final int contentWidth = r - l - contentLeft - getPaddingRight(); 176  177         layoutView(mAuthorText, contentLeft, currentTop, 178                    contentWidth, mAuthorText.getMeasuredHeight()); 179         currentTop += getHeightWithMargins(mAuthorText); 180  181         layoutView(mMessageText, contentLeft, currentTop, 182                 contentWidth, mMessageText.getMeasuredHeight()); 183         currentTop += getHeightWithMargins(mMessageText); 184  185         if (mPostImage.getVisibility() != View.GONE) { 186             layoutView(mPostImage, contentLeft, currentTop, 187                        contentWidth, mPostImage.getMeasuredHeight()); 188  189             currentTop += getHeightWithMargins(mPostImage); 190         } 191  192         final int iconsWidth = contentWidth / mActionIcons.size(); 193         int iconsLeft = contentLeft; 194  195         for (Action action : Action.values()) { 196             final View icon = mActionIcons.get(action); 197  198             layoutView(icon, iconsLeft, currentTop, 199                        iconsWidth, icon.getMeasuredHeight()); 200             iconsLeft += iconsWidth; 201         } 202     } 203  204     @Override 205     public LayoutParams generateLayoutParams(AttributeSet attrs) { 206         return new MarginLayoutParams(getContext(), attrs); 207     } 208  209     @Override 210     protected LayoutParams generateDefaultLayoutParams() { 211         return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 212     } 213  214     @Override 215     public void update(Tweet tweet, EnumSet<UpdateFlags> flags) { 216         mAuthorText.setText(tweet.getAuthorName()); 217         mMessageText.setText(tweet.getMessage()); 218  219         final Context context = getContext(); 220         ImageUtils.loadImage(context, mProfileImage, tweet.getProfileImageUrl(), flags); 221  222         final boolean hasPostImage = !TextUtils.isEmpty(tweet.getPostImageUrl()); 223         mPostImage.setVisibility(hasPostImage ? View.VISIBLE : View.GONE); 224         if (hasPostImage) { 225             ImageUtils.loadImage(context, mPostImage, tweet.getPostImageUrl(), flags); 226         } 227     } 228 } 復制代碼 然後看看他的布局文件    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 <?xml version="1.0" encoding="utf-8"?> <!--   ~ Copyright (C) 2014 Lucas Rocha   ~   ~ 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.   -->   <merge xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="match_parent">       <ImageView         android:id="@+id/profile_image"         android:layout_width="@dimen/tweet_profile_image_size"         android:layout_height="@dimen/tweet_profile_image_size"         android:layout_marginRight="@dimen/tweet_content_margin"         android:scaleType="centerCrop"/>       <TextView         android:id="@+id/author_text"         android:layout_width="fill_parent"         android:layout_height="wrap_content"         android:textColor="@color/tweet_author_text_color"         android:textSize="@dimen/tweet_author_text_size"         android:singleLine="true"/>       <TextView         android:id="@+id/message_text"         android:layout_width="fill_parent"         android:layout_height="wrap_content"         android:layout_marginBottom="@dimen/tweet_content_margin"         android:textColor="@color/tweet_message_text_color"         android:textSize="@dimen/tweet_message_text_size"/>       <ImageView         android:id="@+id/post_image"         android:layout_width="fill_parent"         android:layout_height="@dimen/tweet_post_image_height"         android:layout_marginBottom="@dimen/tweet_content_margin"         android:scaleType="centerCrop"/>       <ImageView         android:id="@+id/reply_action"         android:layout_width="@dimen/tweet_icon_image_size"         android:layout_height="@dimen/tweet_icon_image_size"         android:src="@drawable/tweet_reply"         android:scaleType="fitStart"/>       <ImageView         android:id="@+id/retweet_action"         android:layout_width="@dimen/tweet_icon_image_size"         android:layout_height="@dimen/tweet_icon_image_size"         android:src="@drawable/tweet_retweet"         android:scaleType="fitStart"/>       <ImageView         android:id="@+id/favourite_action"         android:layout_width="@dimen/tweet_icon_image_size"         android:layout_height="@dimen/tweet_icon_image_size"         android:src="@drawable/tweet_favourite"         android:scaleType="fitStart"/>   </merge>      看一下我們就會發現,TweetLayoutView 是通過 onMeasure onlayout 自己來決定子布局的大小和位置的   完全跟linearlayout和relativelayout 沒有任何關系。這樣性能上就有極大的提高。       當然我們不可能自己實現 所有的layout對吧,不然的話 我們就都去谷歌了。。。。哈哈。   但是可以有選擇的把你app裡 ui最復雜的地方 選擇性的優化他。提高 ui渲染的效率。       最後我們看一下前面這個TweetLayoutView  這個布局實際上還不是最優解。   因為裡面有很多系統自帶的imageview 和textview。       我們可以打開一下設置--開發者選項-顯示布局邊界 這個功能   這個功能可以把你當前app的 布局邊界全部標示出來   我們可以打開android 版的gmail 隨便點擊個列表。    可以看一下他們listview裡的每個item 布局邊界都是在外面。裡面沒有任何布局邊界。   所以可以得知gmail的listview裡的 item 是自己重寫的一整個view 裡面沒有使用   任何系統自帶的textview 或者是imageview 之類的。   這樣就是ui終極進化了。。。。。。       當然這麼做 工作量很多,而且很多地方需要考慮。比如你自己畫文本是簡單了,效率是提高了   但是textview 的文本截斷呢?你能做麼?imageview裡的圖片縮放呢?你能做麼?       所以我們在自定義布局的時候 除了考慮ui實現的效率,我們還需要著重考慮實現的難度,和技術上的風險。   個人感覺只需要修改你app最卡頓的地方的布局 即可。尤其是listview viewpager裡面的item   這一般在低端手機上 確實會出現卡幀的現象。其他地方看情況修改。  
你4 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved