Ted*_*opp 16 layout android custom-view
我一直在调整自定义视图的大小和布局问题,我想知道是否有人可以提出"最佳实践"方法.问题如下.想象一下自定义视图,其中内容所需的高度取决于视图的宽度(类似于多行TextView).(显然,这仅适用于布局参数未修复高度的情况.)问题是,对于给定宽度,在这些自定义视图中计算内容高度相当昂贵.特别是,在UI线程上进行计算太昂贵了,因此在某些时候需要启动工作线程来计算布局,并且在完成时,需要更新UI.
问题是,这应该如何设计?我想过几个策略.他们都假设无论何时计算高度,都会记录相应的宽度.
第一个策略显示在此代码中:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
setMeasuredDimension(width, measureHeight(heightMeasureSpec, width));
}
private int measureWidth(int widthMeasureSpec) {
// irrelevant to this problem
}
private int measureHeight(int heightMeasureSpec, int width) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
if (width != mLastWidth) {
interruptAnyExistingLayoutThread();
mLastWidth = width;
mLayoutHeight = DEFAULT_HEIGHT;
startNewLayoutThread();
}
result = mLayoutHeight;
if (specMode == MeasureSpec.AT_MOST && result > specSize) {
result = specSize;
}
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
当布局线程完成时,它将Runnable发布到UI线程以设置mLayoutHeight为计算的高度,然后调用requestLayout()(和invalidate()).
第二种策略是onMeasure始终使用当前当前值mLayoutHeight(不启动布局线程).测试宽度的变化并启动布局线程将通过覆盖来完成onSizeChanged.
第三种策略是懒惰并等待启动布局线程(如果需要)onDraw.
我想尽量减少布局线程的启动和/或杀死次数,同时尽快计算所需的高度.最小化呼叫次数可能会很好requestLayout().
从文档中可以清楚地看到,onMeasure在单个布局过程中可能会多次调用.它不太清楚(但似乎很可能)也onSizeChanged可能被称为几次.因此,我认为将逻辑放入onDraw可能是更好的策略.但这似乎违背了自定义视图大小的精神,所以我对它有一种公认的非理性偏见.
其他人必须面对同样的问题.有没有我错过的方法?有最好的方法吗?
Tim*_*Ohr 10
我认为Android中的布局系统并非真正用于解决这样的问题,这可能会改变问题.
也就是说,我认为这里的核心问题是你的观点实际上不负责计算自己的身高.它始终是视图的父级,用于计算其子级的维度.他们可以表达他们的"意见",但最终,就像在现实生活中一样,他们在这件事上并没有真正的发言权.
这将建议查看视图的父级,或者更确切地说,第一个父级的维度与其子级的维度无关.父母可以拒绝布置(并因此绘制)其子女,直到所有孩子完成他们的测量阶段(这发生在一个单独的线程中).一旦有了,父级就会请求一个新的布局阶段并布置其子级而无需再次测量它们.
重要的是,儿童的测量不会影响所述父母的测量,因此它可以"吸收"第二布局阶段而不必重新测量其子女,从而解决了布局过程.
[编辑]
扩展这一点,我可以想到一个非常简单的解决方案,只有一个小的缺点.你可以简单地创建一个AsyncView扩展,ViewGroup并且,类似于ScrollView,只包含一个总是填满整个空间的子节点.本AsyncView并不认为其子女的测量自己的大小,最好刚好充满可用空间.所有这一切AsyncView做的就是将其子的测量呼叫一个单独的线程,即一旦测量完成回调到视图.
在该视图的内部,你几乎可以放任何你想要的东西,包括其他布局.层次结构中"有问题的视图"的深度并不重要.唯一的缺点是在所有后代都被测量之前,所有后代都不会被渲染.但是你可能想要显示某种加载动画,直到视图准备好了.
"有问题的观点"不需要以任何方式关注多线程.它可以像任何其他视图一样测量自己,花费尽可能多的时间.
[edit2] 我甚至打扰一起快速实施:
package com.example.asyncview;
import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class AsyncView extends ViewGroup {
private AsyncTask<Void, Void, Void> mMeasureTask;
private boolean mMeasured = false;
public AsyncView(Context context) {
super(context);
}
public AsyncView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
for(int i=0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(0, 0, child.getMeasuredWidth(), getMeasuredHeight());
}
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(mMeasured)
return;
if(mMeasureTask == null) {
mMeasureTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... objects) {
for(int i=0; i < getChildCount(); i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
mMeasured = true;
mMeasureTask = null;
requestLayout();
}
};
mMeasureTask.execute();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if(mMeasureTask != null) {
mMeasureTask.cancel(true);
mMeasureTask = null;
}
mMeasured = false;
super.onSizeChanged(w, h, oldw, oldh);
}
}
Run Code Online (Sandbox Code Playgroud)
有关工作示例,请参阅https://github.com/wetblanket/AsyncView
| 归档时间: |
|
| 查看次数: |
1413 次 |
| 最近记录: |