Android 使用适配器在多个水平线性布局中有效地扩展大量视图
我有一个关于如何提高大型水平线性布局的性能的问题 我正在创建一个类似于表的视图,它可以包含50到2500个条目。每个条目都是一个线性布局,包含一个包含一些简单文本的TextView。我已经通过使用实现了设计。此库允许ListAdapter绑定到LinearLayout,以水平或垂直方向显示视图 目前我实现这一点的方法是利用其中两个LinearListView。一种是垂直的who数据,由水平的线性视图组成。这将通过创建表视图提供所需的输出。然后,我用垂直滚动视图将表格包装在水平滚动视图中,以便可以平移表格(向上/向下或向左/向右滚动) 此布局的问题在于,仅在视图初始化时调用adapters getView()(第一次对第一个线性列表进行充气时,会对每个视图进行充气)。一旦每个视图都膨胀,在滚动列表时就不会再次调用getView()方法(当然,它滚动得非常好,因为它已全部加载)。膨胀所需的总时间并没有什么大不了的,但事实上,它膨胀表中的每一项,一行锁定了主UI线程。我需要视图在不阻塞UI线程的情况下“延迟加载”表中的每个项 我有一个屏幕截图,但我没有足够的声誉发布它,也没有足够的时间使用两个链接。我将尝试在评论中添加一个外部照片链接。(参考屏幕截图)表数据是在一组异步任务中生成的,其中每个表项都是以毫秒为单位的当前时间(我知道,由于异步性质,表行没有排序,稍后将对此进行修复)。这个实际的应用程序除了演示这个库之外没有其他用途 我添加了“随机更改数据”按钮,它将创建一个随机点(intx,inty)并生成一个随机字符串,并用该字符串替换(x,y)处的单元格。通过调用适配器的getView()方法,几乎可以立即实现这一点。因此,访问此表非常快!同样,最初的膨胀锁定了主UI线程 需要总结的几个重要注意事项:Android 使用适配器在多个水平线性布局中有效地扩展大量视图,android,performance,android-layout,android-linearlayout,android-adapter,Android,Performance,Android Layout,Android Linearlayout,Android Adapter,我有一个关于如何提高大型水平线性布局的性能的问题 我正在创建一个类似于表的视图,它可以包含50到2500个条目。每个条目都是一个线性布局,包含一个包含一些简单文本的TextView。我已经通过使用实现了设计。此库允许ListAdapter绑定到LinearLayout,以水平或垂直方向显示视图 目前我实现这一点的方法是利用其中两个LinearListView。一种是垂直的who数据,由水平的线性视图组成。这将通过创建表视图提供所需的输出。然后,我用垂直滚动视图将表格包装在水平滚动视图中,以便可以
- UI需要采用表格格式,其中每行可以具有不同的长度,并且可以动态更改。可以从任何零件中删除和添加项目
- My getView()方法(2个适配器和2个LinearListView)利用了ViewHolder模式,并且经过了相当优化李>
- 我的主要目标不是一定要提高总速度,而是高效地加载每个视图,以便主UI线程不会被锁定(我希望在加载表时仍然能够使用该接口)
- 为了简单起见,将每个单元格的内容视为文本视图(稍后可以支持图像和其他复杂组件)
- 表格需要能够向任何方向滚动(不可能一次在屏幕上显示所有内容)
-Evan因为您关心UI性能,所以可以使用AsyncTask抽象类,这是Google推荐的常用类。AsyncTask在UI的单独线程上运行。要使用它,必须创建一个类来对其进行子类化。谷歌网页@,为了您的方便 我找到的代码示例位于。 在代码中,请注意GetItemList扩展了AsyncTask。该类中的OverrideonPostExecute()调用您可能熟悉的setListAdapter方法 上面链接中的代码片段:
private class getItemLists extends
AsyncTask<Void, String, ArrayList<Item>> {
...
@Override
protected String doInBackground(String... params) {
// Good place to add code for time consuming work, no UI access though.
...
}
@Override
protected void onPostExecute(ArrayList<Item> result) {
super.onPostExecute(result);
...
}
私有类getItemList扩展
异步任务{
...
@凌驾
受保护的字符串doInBackground(字符串…参数){
//很好的地方添加代码耗时的工作,但没有用户界面访问。
...
}
@凌驾
受保护的void onPostExecute(ArrayList结果){
super.onPostExecute(结果);
...
}
我从未使用过此任务,但我可能会使用。请随时向我们发布此任务。祝您好运…我已经找到答案:)
@TheOriginalAndroid答案是一个非常好的想法和回应!非常感谢您的时间和帮助。事实上,我已经开始实现一个异步任务管理器,并于昨天上午完成了它
我通过创建一个名为AsycnGridManager的类来解决这个问题,该类将管理负责绘制视图的一组异步任务。这是一段相当多的代码,但我在注释中详细介绍了它。这不是实际的代码,而是一个展示其工作原理的shell。我还没有编译它,所以请不要将其视为菱形。不是吗他的类应该创建并从负责它的主活动或片段中的主线程开始
/**
* This class will manage a view and load it asynchronously.
* In particular, this view will manage a linearLayout in
* 2D space. IE. One verticle linear layout with a horizontal
* linearLayout at each row.
* @author Evan Boucher
*/
public class AsyncGridManager {
/**
* This is the core number of Threads in the pool.
* You should probably consider checking the
* system for the number of cores the device has.
* I currently use 4 as it fits my needs.
*/
private static final int NUM_OF_THREADS_IN_POOL = 4;
/**
* The max number of threads that can exist in the pool at one time.
*/
private static final int MAX_NUM_OF_THREADS_IN_POOL = 10;
/**
* The max number of tasks that the queue can hold for the
* pool
*/
private static final int MAX_NUM_OF_TASKS_IN_QUEUE = 150;
/**
* The max keep alive time for a thread task in the pool.
* This should be longer than your longest task. If you have
* a long UI task in each thread (you are probably doing
* to much to begin with!) then the task may get stopped
* before it finishes.
*/
private static final int THREAD_KEEP_ALIVE_TIME = 4000;
/**
* The minimum time to wait to paint a single EPG item.
* This means that a block will never be painted any faster
* than this number in Milliseconds.
*/
private final int MIN_WAIT_TIME_TO_PAINT = 100;
/**
* The max time an async task will sleep before painting on the
* UI thread.
*/
private final int MAX_WAIT_TIME_TO_PAINT = 1000;
/**
* The thread pool that the async tasks within this class will
* pull from. This is defined by the above varaibles.
*/
private ThreadPoolExecutor mThreadPool;
/**
* The queue of tasks that the thread pool will pull from.
* The size is fairly large as I don't much care about memory
* usage right now. Once the queue fills up it will not add
* anymore tasks. Be aware of that! So tasks can be lost or
* cause a thread to block (if you add the tasks on the main
* thread).
*/
private BlockingQueue taskQueue;
/**
* The thread that this manager will run on as to not block the main thread.
*/
public Thread mGridManagerThread;
/**
* The Grid map object that is the underlying data for this grid.
* Each key is a row and each value is a list for the columns in that
* row.
*/
private Map<String,List<CustomObject>> mGridMap;
//Number of rows in the table (size of the mGridMap)
private int mNumOfRows;
//Get the rootView that is already inflated. This is what we will add to.
private LinearLayout mRootGridView;
//The Android activity context that this special async manager is attached to.
private Context mContext;
/**
* Creates and initializes this class.
*
*/
public AsyncGridManager(Context context, LinearLayout rootView, Map<String,List<CustomObject>> gridMap) {
//Create a new taskqueue for the EPGblocks.
taskQueue = new ArrayBlockingQueue<CreateEPGTableRowTask>(MAX_NUM_OF_TASKS_IN_QUEUE);
//Create a new threadpool for the tasks.
poolExecutor = new ThreadPoolExecutor(NUM_OF_THREADS_IN_POOL, MAX_NUM_OF_THREADS_IN_POOL, THREAD_KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, taskQueue);
this.mGridMap = gridMap;
/*
* We can only get the number of rows as that is predefined
* by this datastructure (won't know how many columns until we get to the row).
*/
this.mNumOfRows = mGridMap.size();
this.mContext = context;
/*
* The RootView should be a LinearLayout in my case and already be inflated!
*/
this.mRootGridView = rootView
}
/**
* Tell the async manager to start loading the tasks into the queue.
* It loads on a seperate thread to make this completely async.
*/
public void startAsyncLoading() {
/*
* It is important here to note that we should inflate the mRootGridView
* This way adding views to it will be async on the UI thread.
*/
mGridManagerThread = new Thread(new AsyncGridLoaderRunnable());
mGridManagerThread.start();
}
/**
* The runnable for this manager to generate
*/
public class AsyncGridLoaderRunnable extends Runnable {
@Override
public void run() {
//A for loop to go through the size of the rows
for (int i = 0; i < mNumOfRows; i++) {
//For each row, lets make a AsyncTask to generate and paint that row. You need to make a new one everytime.
CreateRowAsyncTask rowAsyncTask = new CreateRowAsyncTask(i);
/*
* I pass i in here so that you could also get the rowIndex as a parameter too if we want.
* This adds the task to the taskQueue for this pool to execute.
*/
rowAsyncTask.executeOnExecutor(poolExecutor, i);
}
}
}
/**
* Async Task that will create and print a row
* from the map.
*/
public class CreateRowAsyncTask extends AsyncTask {
//Random generator to force tasks to sleep for random periods.
private Random mRandomGenerator;
//The row index that this task is responsible for painting and managing.
private int rowIndex;
//The horizontal linearlayou that represents this row. Might want to add it to a list so we can reference it later.
private LinearLayout singleRowLayout;
//The local reference to the list of columns for this row.
private List<CustomObject> columnList;
public CreateRowAsyncTask(int rowIndex) {
this.mRandomGenerator = new Random();
this.rowIndex = rowIndex;
//Create the linearlayout for the row.
singleRowLayout = new LinearLayout(mContext);
//Set it to horisontal to be a row.
singleRowLayout.setOrientation(LinearLayout.HORIZONTAL);
//Get a reference to this rows list of columns.
columnList = mGridMap.get(rowIndex);
}
@Override
protected Object doInBackground(Object... arg0) {
/*
* Here you could do some background stuff to setup objects /views.
* I am going to assume you have some method to generate the view
* from our CustomObject (the items within the list for the rows).
*/
//Lets tell the UI thread to add our row real quickly (remember the root view was already inflated)
mRootGridView.addView(singleRowLayout);
/*
* Due to the Async nature we need to draw each row together.
* If we dont, EPG blocks will be out of order (not guaranteed order).
* Uses onProgressUpdate() to paint each block in the row.
*/
CustomObject columnObject;
for (int i = 0; i < columnList.size(); i++) {
//Lets save a reference to the object we want to add to the row we are on
columnObject = columnList.get(i);
/*
* The customView we are adding. This assumes that the columnObject createView() method
* will create a new LinearLayout (or View of some type) which we will add to this row.
* You could put the createView() call directly in the publishProgress() method for
* ease, but I left it out to show the custom view creation.
* Be sure that the createView() does not handle any inflated views (these must be
* accessed on the UI thread).
*/
CustomView newViewToAddAsColumn = columnObject.createView();
//Create each row and use ProgressUpdate to paint it.
publishProgress(newViewToAddAsColumn);
try {
/*
* Sleep the task for a random period of time, this way the view is not loading all at once.
* This is one strategy, there are plenty of other Async Loading strategies
*/
Thread.sleep(mRandomGenerator.nextInt(MAX_WAIT_TIME_TO_PAINT - MIN_WAIT_TIME_TO_PAINT) + MIN_WAIT_TIME_TO_PAINT);
} catch (InterruptedException e) {
Log.e(TAG, "ERROR! AsyncTask failed to wait!!!");
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
protected void onProgressUpdate(Object... values) {
//Get the customView and add it to the row.
CustomView customViewToAdd = (EpgEventView) values[0];
//Add the customView to the row. We assume that the params for the view are within the customView.
singleRowLayout.addView(customViewToAdd, customViewToAdd.getParams());
}
}
}
/**
*此类将管理视图并异步加载它。
*特别是,此视图将管理中的线性布局
*二维空间,即一个垂直线性布局,一个水平布局
*每行的线性布局。
*@作者埃文·鲍彻
*/
公共类异步管理器{
/**
*这是池中线程的核心数。
*你应该考虑检查一下。
*设备拥有的核心数量的系统。
*我目前使用4,因为它适合我的需要。
*/
池中线程的私有静态final int NUM=4;
/**
public class GenerateLayoutAsync extends BaseConCurrentTask<Object> {
private final WeakReference<FragmentActivity> activityReference;
private final LinearLayoutCompat linearLayoutCompat;
private final List<GroupMatch> groupMatchList;
public GenerateLayoutAsync(FragmentActivity context, LinearLayoutCompat linearLayoutCompat, List<GroupMatch> groupMatchList) {
this.activityReference = new WeakReference<>(context);
this.linearLayoutCompat = linearLayoutCompat;
this.groupMatchList = groupMatchList;
}
@Override
public void setUiForLoading() {
}
@Override
public Object call() {
for (int i = 0; i < groupMatchList.size(); i++) {
GroupMatch groupMatch = groupMatchList.get(i);
AppCompatTextView title;
LinearLayoutCompat container;
View itemView = LayoutInflater.from(this.activityReference.get()).inflate(R.layout.group_card, linearLayoutCompat, false);
title = itemView.findViewById(R.id.groupTitle);
container = itemView.findViewById(R.id.populateView);
title.setText(groupMatch.getTitle());
container.setVisibility(View.GONE);
title.setOnClickListener(v -> {
if (container.getVisibility() == View.VISIBLE)
container.setVisibility(View.GONE);
else
container.setVisibility(View.VISIBLE);
});
for (int j = 0; j < groupMatch.getModelList().size(); j++) {
MatchModel matchModel = groupMatch.getModelList().get(j);
AppCompatTextView home, away, middleText, topText, bottomText, betBtn;
AppCompatImageView shareBtn, homeFlag, awayFlag;
View view = LayoutInflater.from(this.activityReference.get()).inflate(R.layout.match_card, (ViewGroup) itemView, false);
home = view.findViewById(R.id.homeTeam);
away = view.findViewById(R.id.awayTeam);
topText = view.findViewById(R.id.topTextV);
middleText = view.findViewById(R.id.middleTextV);
bottomText = view.findViewById(R.id.bottomTextV);
betBtn = view.findViewById(R.id.betNowBtn);
shareBtn = view.findViewById(R.id.shareBtn);
homeFlag = view.findViewById(R.id.homeFlag);
awayFlag = view.findViewById(R.id.awayFlag);
if (CampaignModel.isIsTarget() && CampaignModel.isFetchAds()) {
betBtn.setVisibility(View.VISIBLE);
betBtn.setOnClickListener(v -> this.activityReference.get().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(CampaignModel.getREDIRECT()))));
}
else
betBtn.setVisibility(View.GONE);
home.setText(matchModel.getHome());
away.setText(matchModel.getAway());
home.setSelected(true);
away.setSelected(true);
LocalDateTime localDateTime;
if (matchModel.getHomeScore().isEmpty() && matchModel.getAwayScore().isEmpty()){
betBtn.setAlpha(1f);
betBtn.setEnabled(true);
localDateTime = LocalDateTime.parse(matchModel.getStartDate(), DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm"));
String date = localDateTime.format(DateTimeFormatter.ofPattern("MM/dd/yy"));
String time = localDateTime.format(DateTimeFormatter.ofPattern("HH:mm a"));
topText.setText(time);
bottomText.setText(date);
middleText.setText(null);
}
else{
betBtn.setAlpha(0.3f);
betBtn.setEnabled(false);
localDateTime = LocalDateTime.parse(matchModel.getEndDate(), DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm"));
String date = localDateTime.format(DateTimeFormatter.ofPattern("MM/dd/yy"));
topText.setText(matchModel.getHomeScore());
bottomText.setText(matchModel.getAwayScore());
middleText.setText(date);
}
new Handler(Looper.getMainLooper()).post(() -> {
Glide.with(this.activityReference.get())
.asDrawable()
.load(matchModel.getHomeFlag())
.error(R.drawable.ic_flag)
.into(homeFlag);
Glide.with(this.activityReference.get())
.load(matchModel.getAwayFlag())
.error(R.drawable.ic_flag)
.into(awayFlag);
});
shareBtn.setOnClickListener(v -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this.activityReference.get(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this.activityReference.get(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
ShareToSocial.saveAndShare(matchModel.getHome() + " vs " + matchModel.getAway(), itemView);
else
this.activityReference.get().requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
else
ShareToSocial.saveAndShare(matchModel.getHome() + " vs " + matchModel.getAway(), itemView);
});
new Handler(Looper.getMainLooper()).post(() -> container.addView(view));
}
new Handler(Looper.getMainLooper()).post(() -> linearLayoutCompat.addView(itemView));
}
return null;
}
@Override
public void setDataAfterLoading(Object result) {
}
}