我一直遇到自定义 View 的大小和布局问题,我想知道是否有人可以提出“最佳实践”方法。问题如下。想象一个自定义 View ,其中内容所需的高度取决于 View 的宽度(类似于多行 TextView)。 (显然,这只适用于高度不是由布局参数固定的情况。)问题在于,对于给定的宽度,在这些自定义 View 中计算内容高度相当昂贵。特别是,在 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;
}
当布局线程完成时,它会向 UI 线程发送一个 Runnable 以将 mLayoutHeight 设置为计算出的高度,然后调用 requestLayout()(以及 invalidate ()).
第二种策略是让 onMeasure 始终使用 mLayoutHeight 的当前值(不启动布局线程)。测试宽度变化和启动布局线程将通过覆盖 onSizeChanged 来完成。
第三种策略是懒惰并等待在 onDraw 中启动布局线程(如果需要)。
我想尽量减少布局线程的启动和/或终止次数,同时尽快计算所需的高度。最好也尽量减少对 requestLayout() 的调用次数。
从文档中可以清楚地看出,onMeasure 在单个布局过程中可能会被多次调用。不太清楚(但似乎很可能) onSizeChanged 也可能被多次调用。所以我认为将逻辑放入 onDraw 可能是更好的策略。但这似乎与自定义 View 大小的精神背道而驰,因此我对它有一种不可否认的非理性偏见。
其他人一定也遇到过同样的问题。有没有我错过的方法?有没有最好的方法?
最佳答案
我认为 Android 中的布局系统并不是真正为解决此类问题而设计的,这可能意味着要改变问题。
也就是说,我认为这里的核心问题是您的 View 实际上并不负责计算自己的高度。计算其子级尺寸的始终是 View 的父级。他们可以发表自己的“意见”,但最终,就像在现实生活中一样,他们在这件事上并没有真正的发言权。
这将建议查看 View 的父级,或者更确切地说,查看其尺寸独立于其子级尺寸的第一个父级。该 parent 可以拒绝布局(并因此绘制)它的 child ,直到所有 child 都完成了他们的测量阶段(这发生在一个单独的线程中)。一旦有了,父级请求一个新的布局阶段并对其子级进行布局,而无需再次测量它们。
重要的是,子级的测量不影响所述父级的测量,这样它就可以“吸收”第二个布局阶段而不必重新测量其子级,从而解决布局过程。
[编辑]
稍微扩展一下,我可以想到一个非常简单的解决方案,它只有一个小缺点。您可以简单地创建一个扩展 ViewGroup 的 AsyncView,并且与 ScrollView 类似,只包含一个始终填满其整个空间的子项。 AsyncView 不会将其子项的测量值视为自己的大小,理想情况下只是填充可用空间。 AsyncView 所做的只是将其子项的测量调用包装在一个单独的线程中,该线程在测量完成后立即回调 View 。
在该 View 中,您几乎可以放置任何您想要的内容,包括其他布局。 “有问题的观点”在层次结构中有多深并不重要。唯一的缺点是在测量完所有后代之前不会渲染任何后代。但是您可能希望在 View 准备好之前显示某种加载动画。
“有问题的观点”不需要以任何方式关注多线程。它可以像任何其他 View 一样测量自己,根据需要花费尽可能多的时间。
[编辑2] 我什至费心拼凑一个快速的实现:
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);
}
}
关于android - 处理昂贵的 View 高度计算的最佳实践?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7015097/
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c
目前,Itembelongs_toCompany和has_manyItemVariants。我正在尝试使用嵌套的fields_for通过Item表单添加ItemVariant字段,但是使用:item_variants不显示该表单。只有当我使用单数时才会显示。我检查了我的关联,它们似乎是正确的,这可能与嵌套在公司下的项目有关,还是我遗漏了其他东西?提前致谢。注意:下面的代码片段中省略了不相关的代码。编辑:不知道这是否相关,但我正在使用CanCan进行身份验证。routes.rbresources:companiesdoresources:itemsenditem.rbclassItemi
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路