使用适配器有效地膨胀多个水平LinearLayout中的许多视图 [英] Efficiently Inflating a lot of Views within several Horizontal LinearLayout with Adapters

查看:72
本文介绍了使用适配器有效地膨胀多个水平LinearLayout中的许多视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对如何提高大型水平线性布局的性能有疑问.

I have a question about how to improve the performance of a large horizontal linear layout.

我正在创建一个类似view的表,该表可以包含50到2500个条目.每个条目都是一个LinearLayout,其中包含带有一些简单文本的TextView.我已经利用 LinearListView库实现了该设计.该库允许将ListAdapter绑定到LinearLayout以在水平或垂直方向上显示视图.

I am creating a table like view that can have anywhere from 50 to 2,500 entries. Each entry is a LinearLayout containing a TextView with some simple text. I have implemented the design by utilizing the LinearListView library. This library allows an ListAdapter to be bound to a LinearLayout to display a view in a horizontal or vertical orientation.

我目前实现此目的的方式是利用其中两个LinearListViews.一个是垂直的,谁的数据由水平的LinearListViews组成.通过创建表视图,可以提供所需的输出.然后,我将表包装在带有Verticle ScrollView的Horizo​​ntal ScrollView中,以便可以平移该表(向上/向下或向左/向右滚动).

The way I have implemented this currently is by utilizing two of these LinearListViews. One is vertical who data consists of horizontal LinearListViews. This gives the desired output by creating a table view. I then wrap the table within a Horizontal ScrollView with a Verticle ScrollView so that the table can be panned (scrolled in either up/down or left/right).

这种布局的问题在于,适配器getView()仅在视图初始化时调用(第一次将其膨胀为第一个linearList时,它将膨胀每个视图).放大每个视图后,在滚动列表时就永远不会再次调用getView()方法(它会滚动得很好,因为它全部都已加载).膨胀所需的总时间并不算多,但实际上膨胀表中的表中的每个项目都会锁定主UI线程.我需要视图延迟加载"表中的每个项目而不阻塞UI线程.

The issue with this layout is that the adapters getView() is called only at the initialization of the views (the first time it inflates the first linearList it inflates every view). Once each view is inflated, the getView() method is never called again while scrolling through the list (it scrolls very well of course as it is all loaded). The total amount of time it takes to inflate is not as big of deal, but the fact that it inflates every item in the table in a row locks up the main UI thread. I need the view to "lazy load" each item in the table without blocking the UI thread.

我有一个屏幕截图,但是我没有足够的声誉来发布它,也没有足够的资源使用两个链接.我将尝试在评论中添加外部照片链接.(参考屏幕截图)表数据是在一组异步任务中生成的,其中每个表项都是当前时间(以毫秒为单位)(我知道由于异步的性质,表行没有排序,以后会进行修复).这个实际的应用程序除了演示该库之外没有其他用途.

I have a screen shot, but I do not have enough reputation to post it nor do I have enough to use two links. I will attempt to put a external photo link in the comments. (In reference to the screenshot) The table data is generated in a group of async tasks where each table item is the current time in Milliseconds (I am aware that the table rows are not ordered due to the async nature and will fix that later). This actual app serves no purpose other than to demonstrate this library.

我添加了随机更改数据"按钮,该按钮将创建一个随机点(int x,int y)并生成一个随机字符串,然后用该字符串替换(x,y)处的单元格.这几乎是通过调用特定适配器的getView()方法立即发生的.因此访问该表非常快!再次,是最初的膨胀锁定了主UI线程.

I added the "Change Data Randomly" button which will create a random point (int x,int y) and generate a random string and replace the cell at (x,y) with that string. This happens almost instantly by calling that particulars adapter's getView() method. So access to this table is very quick! Again, it is the initial inflating that is locking the main UI thread.

一些重要的笔记进行总结:

A few important notes to summarize:

  • UI必须采用表格格式,其中每一行的长度可以不同,并且可以动态更改.可以从任何部分删除和添加项目.
  • 我的getView()方法(2个适配器和2个LinearListViews)正在使用ViewHolder模式,并且进行了相当优化.
  • 我的主要目标不是一定要提高总体速度,而是要有效地加载每个视图,以使主UI线程不会被锁定(我希望在加载表时仍能够使用该界面).
  • 为简单起见,请将每个单元格的内容视为文本视图(稍后可以支持图像和其他复杂组件).
  • 表格必须能够在任何方向上滚动(不可能一次在屏幕上显示其所有内容).

我找到了此应用程序(电视SideView),它会创建一个相当大的表格视图,加载效果非常好.我最终希望实现类似于此的功能(请查看程序指南"页面以查看实际表).它会加载一堆单元格,您仍然可以使用UI(在第一次打开表格时拖动表格,您会看到单元格正在加载).

I have found this application (TV SideView) which creates a fairly large table view that loads really nicely. I ultimately would want to achieve something similar to this (look at the "Program Guide" page to see the actual table). It loads a bunch of cells and you can still use the UI (drag the table around when it first opens and you will see the cells loading).

我会继续努力,并将发现的新内容发回去.

I will keep chugging away at this and post back anything new I find.

任何建议和帮助将不胜感激!非常感谢您的宝贵时间!

Any advice and help would be greatly appreciated! Thank you so much for your time!

-埃文

推荐答案

我知道了:)

@TheOriginalAndroid答案是一个绝妙的主意和回应!非常感谢您的时间和帮助.实际上,我已经开始实现AsyncTask Manager,并于昨天早晨完成了它.

@TheOriginalAndroid answer is an excellent idea and response! Thank you so much for your time and help. I actually had already started implementing an AsyncTask Manager and finished it yesterday morning.

我通过创建一个名为AsycnGridManager的类解决了此问题,该类将管理负责绘制视图的asyncTasks组.这是相当多的代码,但是我在注释中进行了详细介绍.这不是实际的代码,而是一个外壳,用于显示其工作原理的概述.我还没有编译它,所以请不要把它当作钻石.该类应从您负责它的主要活动或片段中的主线程创建并启动.

I solved this by creating a class called AsycnGridManager that will manage the group of asyncTasks responsible for painting the view. It is quite a bit of code, but I went into great detail in the comments. This is not the actual code but a shell to show an overview of how it works. I have not compiled it so please don't take it as a diamond. This class should be created and started from the main thread within your main activity or fragment that is responsible for it.

/**
 * 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());
        }



     }

   }

我没有专门运行此代码,因此将其用作示例而不是完美的解决方案.此代码将异步将视图添加到rootView,而不会阻止UI体验.:)

I have not run this code specifically so use it more as an example than a perfect solution. This code will add views asynchronously to the rootView without blocking the UI experience. :)

享受,

-埃文

这篇关于使用适配器有效地膨胀多个水平LinearLayout中的许多视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆