8

「コラージュ」形式で次のレイアウトを使用して、約200のImageView(ランダムな高さ)のリストを作成したいと思います。

ここに画像の説明を入力してください

通常、アダプタを使用して得られるパフォーマンスのためにListViewでこれを行いますが、画像を列に表示し、画像によって高さを変えて(画像のを参照)、この目的で単一のリストビューを使用することはできません。 。

私はこのレイアウトを次のように実装しようとしました:

  • 同期スクロールを使用した3つのListView=低速
  • 各行に3つの画像を含む単一のListView=異なる高さを許可しない
  • GridView=異なる高さを許可しない
  • GridLayout=プログラムでさまざまな高さを実装するのは困難です。アダプターがないため、OutOfMemoryErrorsが一般的です
  • FlowLayout =アダプターがないため、OutOfMemoryErrorsが一般的です
  • 3つのVerticalLinearLayoutsを使用したScrollView=これまでのところ最良の解決策ですが、OutOfMemoryErrorsが一般的です

ScrollViewで3つのLinearLayoutを使用することになりましたが、これは最適とは言えません。私はむしろアダプターで何かを使いたいです。

編集 私は以下の応答のようにStaggeredGridViewを見てきましたが、かなりバグがあります。より安定したこれの実装はありますか?

4

6 に答える 6

1

私はあなたのために実用的な解決策を持っていると思います。

ここで言及されている主なファイルは、http://pastebin.com/u/morganbelfordのPasteBinにもあります

私は基本的に、優れたLoopJのセットを使用して、前述のgithubプロジェクトに相当する簡略化されたhttps://github.com/maurycyw/StaggeredGridViewSmartImageViewsを実装しました。

私のソリューションは、ほど一般的で柔軟性がありませんが、StaggeredGridViewうまく機能しているようです。機能上の大きな違いの1つは、画像を常に左から右にレイアウトしてから、再び左から右にレイアウトすることです。次の画像を最短の列に配置しようとはしません。これにより、ビューの下部が少し不均一になりますが、Webからの初期ロード中に発生するシフトは少なくなります。

3つの主要なクラスがあります。カスタムは、データオブジェクトのセットを管理するStagScrollViewカスタムStagLayout(サブクラス化された)を含みます。FrameLayoutImageInfo

レイアウトstag_layout.xmlを次に示します(画像サイズに基づいてコードで再計算されるため、1000dpの初期の高さは関係ありません):

// stag_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.morganbelford.stackoverflowtest.pinterest.StagScrollView xmlns:a="http://schemas.android.com/apk/res/android"
 a:id="@+id/scroller"
 a:layout_width="match_parent"
 a:layout_height="match_parent" >

  <com.morganbelford.stackoverflowtest.pinterest.StagLayout
    a:id="@+id/frame"
    a:layout_width="match_parent"
    a:layout_height="1000dp"
    a:background="@drawable/pinterest_bg" >
  </com.morganbelford.stackoverflowtest.pinterest.StagLayout>

</com.morganbelford.stackoverflowtest.pinterest.StagScrollView>

これが、レイアウトを使用するメインのアクティビティです。 ちょうど基本的に、onCreate使用するURL、各画像間のマージン、および列の数を示します。モジュール性を高めるために、これらのパラメーターをStagScrollViewに渡すこともできます(StagLayoutが含まれていますが、スクロールビューはとにかくレイアウトに渡す必要があります)。StagActivityStagLayout

// StagActivity.onCreate
setContentView(R.layout.stag_layout);

StagLayout container = (StagLayout) findViewById(R.id.frame);

DisplayMetrics metrics = new DisplayMetrics();
((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);  
float fScale = metrics.density;


String[] testUrls = new String[] { 
    "http://www.westlord.com/wp-content/uploads/2010/10/French-Bulldog-Puppy-242x300.jpg", 
    "http://upload.wikimedia.org/wikipedia/en/b/b0/Cream_french_bulldog.jpg",
    "http://bulldogbreeds.com/breeders/pics/french_bulldog_64368.jpg",
    "http://www.drsfostersmith.com/images/articles/a-french-bulldog.jpg",
    "http://2.bp.blogspot.com/-ui2p5Z_DJIs/Tgdo09JKDbI/AAAAAAAAAQ8/aoTdw2m_bSc/s1600/Lilly+%25281%2529.jpg",
    "http://www.dogbreedinfo.com/images14/FrenchBulldog7.jpg",
    "http://dogsbreed.net/wp-content/uploads/2011/03/french-bulldog.jpg",
    "http://www.theflowerexpert.com/media/images/giftflowers/flowersandoccassions/valentinesdayflowers/sea-of-flowers.jpg.pagespeed.ce.BN9Gn4lM_r.jpg",
    "http://img4-2.sunset.timeinc.net/i/2008/12/image-adds-1217/alcatraz-flowers-galliardia-m.jpg?300:300",
    "http://images6.fanpop.com/image/photos/32600000/bt-jpgcarnation-jpgFlower-jpgred-rose-flow-flowers-32600653-1536-1020.jpg",
    "http://the-bistro.dk/wp-content/uploads/2011/07/Bird-of-Paradise.jpg",
    "http://2.bp.blogspot.com/_SG-mtHOcpiQ/TNwNO1DBCcI/AAAAAAAAALw/7Hrg5FogwfU/s1600/birds-of-paradise.jpg",
    "http://wac.450f.edgecastcdn.net/80450F/screencrush.com/files/2013/01/get-back-to-portlandia-tout.jpg",
    "http://3.bp.blogspot.com/-bVeFyAAgBVQ/T80r3BSAVZI/AAAAAAAABmc/JYy8Hxgl8_Q/s1600/portlandia.jpg",
    "http://media.oregonlive.com/ent_impact_tvfilm/photo/portlandia-season2jpg-7d0c21a9cb904f54.jpg",
    "https://twimg0-a.akamaihd.net/profile_images/1776615163/PortlandiaTV_04.jpg",
    "http://getvideoartwork.com/gallery/main.php?g2_view=core.DownloadItem&g2_itemId=85796&g2_serialNumber=1",
    "http://static.tvtome.com/images/genie_images/story/2011_usa/p/portlandia_foodcarts.jpg",
    "http://imgc.classistatic.com/cps/poc/130104/376r1/8728dl1_27.jpeg",

    };
container.setUrls(testUrls, fScale * 10, 3); // pass in pixels for margin, rather than dips

ソリューションの要点に入る前に、ここに単純なStagScrollViewサブクラスがあります。彼の唯一の特別な振る舞いは、彼の主な子供(私たちStagLayout)に現在表示されている領域を伝えることです。これにより、彼は実現されたサブビューの可能な限り少ない数を効率的に使用できます。

// StagScrollView
StagLayout _frame;

@Override
protected void onFinishInflate() {
    super.onFinishInflate();

    _frame = (StagLayout) findViewById(R.id.frame);

}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    if (oldh == 0)
        _frame.setVisibleArea(0, h);
}


@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);
    _frame.setVisibleArea(t, t + getHeight());
}

次に、最も重要なクラスがありStagLayoutます。

まず、setUrlsデータ構造を設定します。

public void setUrls(String[] urls, float pxMargin, int cCols)
{
    _pxMargin = pxMargin;
    _cCols = cCols;
    _cMaxCachedViews = 2 * cCols;
    _infos = new ArrayList<ImageInfo>(urls.length);  // should be urls.length

    for (int i = 0; i < 200; i++)  // should be urls.length IRL, but this is a quick way to get more images, by using repeats
    {
        final String sUrl = urls[i % urls.length]; // could just be urls[i] IRL
        _infos.add(new ImageInfo(sUrl, new OnClickListener() {

            @Override
            public void onClick(View v) {
                Log.d("StagLayout", String.format("Image clicked: url == %s", sUrl));
            }
        }));
    }

    _activeInfos = new HashSet<ImageInfo>(_infos.size());
    _cachedViews = new ArrayList<SmartImageView>(_cMaxCachedViews);

    requestLayout();  // perform initial layout

}

私たちの主なデータ構造ImageInfoです。これは一種の軽量プレースホルダーであり、必要なときに各画像が表示される場所を追跡できます。子ビューをレイアウトするときは、ImageInfoの情報を使用して、実際のビューを配置する場所を特定します。ImageInfoについて考える良い方法は、「仮想イメージビュー」としてです。

詳細については、インラインのコメントを参照してください。

public class ImageInfo {

private String _sUrl;

// these rects are in float dips
private RectF _rLoaded;  // real size of the corresponding loaded SmartImageView
private RectF _rDefault; // lame default rect in case we don't have anything better to go on
private RectF _rLayout;  // rect that our parent tells us to use -- this corresponds to a real View's layout rect as specified when parent ViewGroup calls child.layout(l,t,r,b)

private SmartImageView _vw;

private View.OnClickListener _clickListener;

public ImageInfo(String sUrl, View.OnClickListener clickListener) {
    _rDefault = new RectF(0, 0, 100, 100);
    _sUrl = sUrl;
    _rLayout = new RectF();
    _clickListener = clickListener;
}

// Bounds will be called by the StagLayout when it is laying out views.
// We want to return the most accurate bounds we can.
public RectF bounds() {
    // if there is not yet a 'real' bounds (from a loaded SmartImageView), try to get one
    if (_rLoaded == null && _vw != null) {
        int h = _vw.getMeasuredHeight();
        int w = _vw.getMeasuredWidth();

        // if the SmartImageView thinks it knows how big it wants to be, then ok
        if (h > 0 && w > 0) {  
            _rLoaded = new RectF(0, 0, w, h);
        }
    }
    if (_rLoaded != null)
        return _rLoaded;

    // if we have not yet gotten a real bounds from the SmartImageView, just use this lame rect
    return _rDefault;
}

// Reuse our layout rect -- this gets called a lot
public void setLayoutBounds(float left, float top, float right, float bottom) {
    _rLayout.top = top;
    _rLayout.left = left;
    _rLayout.right = right;
    _rLayout.bottom = bottom;
}

public RectF layoutBounds() {
    return _rLayout;
}

public SmartImageView view() {
    return _vw;
}

// This is called during layout to attach or detach a real view
public void setView(SmartImageView vw) 
{
    if (vw == null && _vw != null)
    {
        // if detaching, tell view it has no url, or handlers -- this prepares it for reuse or disposal 
        _vw.setImage(null, (SmartImageTask.OnCompleteListener)null);
        _vw.setOnClickListener(null);
    }

    _vw = vw;

    if (_vw != null)
    {
        // We are attaching a view (new or re-used), so tell it its url and attach handlers.
        // We need to set this OnCompleteListener so we know when to ask the SmartImageView how big it really is
        _vw.setImageUrl(_sUrl, R.drawable.default_image, new SmartImageTask.OnCompleteListener() {
            final private View vw = _vw;
            @Override
            public void onComplete() {
                vw.measure(MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED));
                int h = vw.getMeasuredHeight();
                int w = vw.getMeasuredWidth();
                _rLoaded = new RectF(0, 0, w, h);
                Log.d("ImageInfo", String.format("Settings loaded size onComplete %d x %d for %s", w, h, _sUrl));
            }
        });
        _vw.setOnClickListener(_clickListener);
    }
}

// Simple way to answer the question, "based on where I have laid you out, are you visible"
public boolean overlaps(float top, float bottom) {
    if (_rLayout.bottom < top)
        return false;
    if (_rLayout.top > bottom)
        return false;

    return true;
}

}

残りの魔法はとで起こりStagLayout's onMeasureますonLayout

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int width = MeasureSpec.getSize(widthMeasureSpec);

    // Measure each real view that is currently realized. Initially there are none of these
    for (ImageInfo info : _activeInfos)
    {
        View v = info.view();
        v.measure(MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED));
    }

    // This arranges all of the imageinfos every time, and sets _maxBottom
    // 
    computeImageInfo(width);  
    setMeasuredDimension(width, (int)_maxBottom);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

    // This figures out what real SmartImageViews we need, creates new ones, re-uses old ones, etc.  
    // After this call _activeInfos is correct -- the list of ImageInfos that are currently attached to real SmartImageViews
    setupSubviews();


    for (ImageInfo info : _activeInfos)
    {
        // Note: The layoutBounds of each info is actually computed in onMeasure
        RectF rBounds = info.layoutBounds();  
        // Tell the real view where it should be
        info.view().layout((int)rBounds.left, (int)rBounds.top, (int)rBounds.right, (int)rBounds.bottom);    
    }

}

では、実際にすべてのImageInfoをどのように配置するかを見てみましょう。

private void computeImageInfo(float width)
{
    float dxMargin = _pxMargin; 
    float dyMargin = _pxMargin;

    float left = 0;
    float tops[] = new float[_cCols];  // start at 0
    float widthCol = (int)((width - (_cCols + 1) * dxMargin) / _cCols);

    _maxBottom = 0;

    // layout the images -- set their layoutrect based on our current location and their bounds
    for (int i = 0; i < _infos.size(); i++)
    {
        int iCol = i % _cCols;
        // new row
        if (iCol == 0)
        {
           left = dxMargin;
           for (int j = 0; j < _cCols; j++)
               tops[j] += dyMargin;
        }
        ImageInfo info = _infos.get(i); 
        RectF bounds = info.bounds();
        float scale = widthCol / bounds.width(); // up or down, for now, it does not matter
        float layoutHeight = bounds.height() * scale;
        float top = tops[iCol];
        float bottom = top + layoutHeight;
        info.setLayoutBounds(left, top, left + widthCol, bottom);

        if (bottom > _maxBottom)
            _maxBottom = bottom;
        left += widthCol + dxMargin;
        tops[iCol] += layoutHeight;
    }

    // TODO Optimization: build indexes of tops and bottoms
    //  Exercise for reader

    _maxBottom += dyMargin;
}

そして、今、私たちがどのように本物を作成し、再利用し、処分するかを見てみましょSmartImageViewsonLayout

private void setupSubviews()
{

    // We need to compute new set of active views

    // TODO Optimize enumeration using indexes of tops and bottoms

    // NeededInfos will be set of currently visible ImageInfos
    HashSet<ImageInfo> neededInfos = new HashSet<ImageInfo>(_infos.size());
    // NewInfos will be subset that are not currently assigned real views
    HashSet<ImageInfo> newInfos = new HashSet<ImageInfo>(_infos.size());
    for (ImageInfo info : _infos)
    {
        if (info.overlaps(_viewportTop, _viewportBottom))
        {
            neededInfos.add(info);
            if (info.view() == null)
                newInfos.add(info);
        }
    }

    // So now we have the active ones. Lets get any we need to deactivate.    
    // Start with a copy of the _activeInfos from last time
    HashSet<ImageInfo> unneededInfos = new HashSet<ImageInfo>(_activeInfos); 

    // And remove all the ones we need now, leaving ones we don't need any more
    unneededInfos.removeAll(neededInfos);

    // Detach all the views from these guys, and possibly reuse them
    ArrayList<SmartImageView> unneededViews = new ArrayList<SmartImageView>(unneededInfos.size());
    for (ImageInfo info : unneededInfos)
    {
        SmartImageView vw = info.view();
        unneededViews.add(vw);
        info.setView(null); // at this point view is still a child of parent
    }

    // So now we try to reuse the views, and create new ones if needed
    for (ImageInfo info : newInfos)
    {
        SmartImageView vw = null;
        if (unneededViews.size() > 0)
        {
            vw = unneededViews.remove(0); // grab one of these -- these are still children and so dont need to be added to parent
        }
        else if (_cachedViews.size() > 0)
        {
            vw = _cachedViews.remove(0);  // else grab a cached one and re-add to parent
            addViewInLayout(vw, -1, new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        }
        else 
        {
            vw = new SmartImageView(getContext()); // create a whole new one
            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            addViewInLayout(vw, -1, lp);  // and add to parent
        }
        info.setView(vw);  // info should also set its data
    }

    // At this point, detach any unneeded views and add to our cache, up to limit
    for (SmartImageView vw : unneededViews)
    {
        // tell view to cancel
        removeViewInLayout(vw);  // always remove from parent
        if (_cachedViews.size() < _cMaxCachedViews)
            _cachedViews.add(vw);
    }


    // Record the active ones for next time around
    _activeInfos = neededInfos;

}

_viewportTopと_viewportBottomは、ユーザーがスクロールするたびに設定されることに注意してください。

// called on every scroll by parent StagScrollView
public void setVisibleArea(int top, int bottom) {

    _viewportTop = top;
    _viewportBottom = bottom;

    //fixup views
    if (getWidth() == 0) // if we have never been measured, dont do this - it will happen in first layout shortly
        return;
    requestLayout();
}
于 2013-01-08T19:47:24.980 に答える
1

https://github.com/maurycyw/StaggeredGridViewを見ることができます

私は個人的にそれを扱ったことがありませんが、少なくともいくつかの概念を盗むことができます。

于 2012-12-29T18:38:25.283 に答える
0
  1. レイアウトにリストビューを作成します。
  2. リストビューと同じ背景の別のレイアウトを作成します。背景レイアウトは、3つの画像ビュー(隣り合っている、つまり互いに右側)で、プロパティが水平方向にWrap_Contentに設定され、画像ビューがWrap_Contentに配置されているViewsプロパティ全体が設定されています。 。
  3. listviewアダプタのgetview()メソッドでレイアウトを膨らませます。これでは、膨らんだレイアウトの画像ビューに3セットの画像を設定する必要があります。

お役に立てれば!

于 2013-01-05T08:07:35.123 に答える
0

カスタムリストにラップされた現在のソリューションを使用できませんか?

各行のgetViewメソッドで、既存のソリューションを拡張します(もちろん、変換ビューをチェックします)。つまり、3つの垂直LinearLayoutsを備えたScrollViewです。

于 2013-01-08T10:37:22.030 に答える
0

私はそれが3つの独立したリストビューで実装できると思います、あなたがそれをしなければならないのは、imageviewのレイアウトを膨らませてそれをlistviewに追加するためだけです。

インフレーション中のレイアウトパラメータとして以下を使用します。

レイアウトの幅:match_parentレイアウトの高さ:wrap_contentレイアウトの重みを.3として、layout_widthを0dp、heightをfill_parentとして3つのリストビューすべてに割り当てることができます。

お役に立てれば。

于 2013-01-08T05:47:44.350 に答える
0

3リストビューソリューションが遅い理由を知っていますか?

各列にはいくつの異なるサイズがありますか?ビューのリサイクルを効率的に行うには、画像のサイズごとにビュータイプを作成してから、getItemViewTypeを使用して、正しいタイプのビューをリサイクルしていることを確認する必要があると思います。そうしないと、リサイクルのメリットがあまり得られません。画像ビューのソースをリセットできるようにするだけです。

于 2013-01-08T16:02:05.913 に答える