我一直在考虑自定义视图的大小调整和布局问题,我想知道是否有人可以提出“最佳实践”方法。问题如下。想象一下自定义视图,其中内容所需的高度取决于视图的宽度(类似于多行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;
}
当布局线程完成时,它会将一个Runnable发布到UI线程,以将mLayoutHeight
设置为计算的高度,然后调用requestLayout()
(和invalidate()
)。
第二种策略是让onMeasure
始终使用mLayoutHeight
的当前当前值(不启动布局线程)。测试宽度的变化并启动布局线程将通过覆盖onSizeChanged
来完成。
第三种策略是懒惰并等待onDraw
中的布局线程(如有必要)。
我想尽量减少布局线程的启动和/或杀死次数,同时尽快计算所需的高度。最好尽量减少对requestLayout()
的调用次数。
从文档中可以清楚地看到,在单个布局过程中可能会多次调用onMeasure
。不太清楚(但似乎很可能)onSizeChanged
也可能被多次调用。所以我认为将逻辑放在onDraw
可能是更好的策略。但这似乎与定制观点大小的精神背道而驰,所以我对此持有一种不合理的偏见。
其他人必须面对同样的问题。有没有我错过的方法?有最佳方法吗?
答案 0 :(得分:10)
我认为Android中的布局系统并非真正设计用于解决这样的问题,这可能会改变问题。
那就是说,我认为这里的核心问题是你的观点实际上不负责计算自己的身高。它始终是视图的父级,用于计算其子级的维度。他们可以表达他们的“意见”,但最终,就像在现实生活中一样,他们在这件事上并没有真正的发言权。
这将建议查看视图的父级,或者更确切地说,第一个父级的维度与其子级的维度无关。父母可以拒绝布置(并因此绘制)其子女,直到所有孩子完成他们的测量阶段(这发生在一个单独的线程中)。一旦有了,父母就会请求一个新的布局阶段并布置其子项,而不必再次测量它们。
重要的是,孩子的测量不会影响所述父母的测量,这样它就可以“吸收”第二个布局阶段,而不必重新测量其子项,从而解决布局过程。
<强> [编辑] 强>
稍微扩展一点,我可以想到一个非常简单的解决方案,只有一个小的缺点。您只需创建一个扩展AsyncView
的{{1}},与ViewGroup
类似,只会包含一个始终填充整个空间的子项。 ScrollView
并不认为其子项的大小有其自身的大小,理想情况下只是填补了可用空间。所有AsyncView
所做的就是将其子项的测量调用包装在一个单独的线程中,一旦测量完成,就会回调该视图。
在该视图的内部,你几乎可以放任何你想要的东西,包括其他布局。层次结构中“有问题的视图”的深度并不重要。唯一的缺点是在所有后代都被测量之前,所有后代都不会被渲染。但是你可能想要显示某种加载动画,直到视图准备就绪。
“有问题的观点”不需要以任何方式关注多线程。它可以像任何其他视图一样测量自己,花费尽可能多的时间。
<强> [EDIT2] 强> 我甚至不愿意快速实施快速实施:
AsyncView
有关工作示例,请参阅https://github.com/wetblanket/AsyncView
答案 1 :(得分:1)
昂贵计算的一般方法是memoization - 缓存计算结果,希望可以再次使用这些结果。我不知道memoization在这里适用得多好,因为我不知道相同的输入数字是否可以多次出现。
答案 2 :(得分:0)
你也可以做这样的策略:
创建自定义子视图:
public class CustomChildView extends View
{
MyOnResizeListener orl = null;
public CustomChildView(Context context)
{
super(context);
}
public CustomChildView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public CustomChildView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public void SetOnResizeListener(MyOnResizeListener myOnResizeListener )
{
orl = myOnResizeListener;
}
@Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld)
{
super.onSizeChanged(xNew, yNew, xOld, yOld);
if(orl != null)
{
orl.OnResize(this.getId(), xNew, yNew, xOld, yOld);
}
}
}
并创建一些自定义侦听器,如:
public class MyOnResizeListener
{
public MyOnResizeListener(){}
public void OnResize(int id, int xNew, int yNew, int xOld, int yOld){}
}
您将侦听器实例化为:
Class MyActivity extends Activity
{
/***Stuff***/
MyOnResizeListener orlResized = new MyOnResizeListener()
{
@Override
public void OnResize(int id, int xNew, int yNew, int xOld, int yOld)
{
/***Handle resize event and call your measureHeight(int heightMeasureSpec, int width) method here****/
}
};
}
不要忘记将听众传递给自定义视图:
/***Probably in your activity's onCreate***/
((CustomChildView)findViewById(R.id.customChildView)).SetOnResizeListener(orlResized);
最后,您可以通过执行以下操作将CustomChildView添加到XML布局:
<com.demo.CustomChildView>
<!-- Attributes -->
<com.demo.CustomChildView/>
答案 3 :(得分:0)
Android开始时不知道实际大小,需要计算它。完成后,onSizeChanged()
会以实际尺寸通知您。
onSizeChanged()
。事件不一定来自用户。当android改变大小时,调用onSizeChanged()
。与onDraw()
相同的是,当应该绘制视图时onDraw()
被调用。
onMeasure()
后立即自动调用 measure()
Some other related link for you
顺便说一下,谢谢你在这里提出这样一个有趣的问题。 很乐意提供帮助。:)
答案 4 :(得分:0)
有操作系统提供的小部件,操作系统提供的事件/回调系统,操作系统提供的布局/约束管理,操作系统提供的数据绑定到小部件。总而言之,我将其称为操作系统提供的UI API。 无论操作系统提供的UI API的质量如何,您的应用程序有时可能无法使用它的功能得到有效解决。因为他们可能在设计时考虑了不同的想法。 因此,总有一个很好的选择来抽象,并创建自己的 - 面向任务的UI API,而不是操作系统为您提供的API。 您自己提供的api可以让您计算和存储,然后以更有效的方式使用不同的小部件大小。减少或消除瓶颈,线程间,回调等 有时创建这样的layer-api只需要几分钟,或者有时它可以提升到足以成为整个项目的最大部分。 长时间调试和支持它可能需要一些努力,这是这种方法的主要缺点。 但优点是你的思维定型变化。你开始思考“热门创造”而不是“如何找到限制的方式”。 我不知道“最佳实践”,但这可能是最常用的方法之一。 通常你会听到“我们使用自己的”。选择自制和大家伙并不容易。保持理智很重要。