I have a listView in my screen. This listView fills 2/3 of my screen (so its height varies on different devices). I have - lets say 50 items in my array list - but I need to put X number of items in it which is visible to the user (e.g on small screen 3, on normal screen 5, on tablet 8).
So the way I'm doing is getting the height of listView in PX, then get its DP size and since I know the height of each row is 60dp then divide height of listViwe in DP format to height of row (which 60dp). So it returns number of items that will be visible to the user and I pass this number to getCount() method of my adapter to populate this amount of items.
This is my code, I have an observer that will be assigned some where in onCreate() method like this this.mListView.getViewTreeObserver().addOnGlobalLayoutListener(mListViewGlobalListener);
ViewTreeObserver.OnGlobalLayoutListener mListViewGlobalListener = new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
removeListViewListener();
if (mListView == null || mAdapter == null)
{
return;
}
int heightInPx = mListView.getHeight();
int heightInDp = pixelsToDp(heightInPx, getResources().getDisplayMetrics());
int maxDisplayDriver = heightInDp / 60;
mAdapter.setMaxDisplayDrivers(maxDisplayDriver);
Logger.error("TEST", "heightInPx:" + heightInPx + ", heightInDp:" + heightInDp + ", maxDisplayDriver:" + maxDisplayDriver);
}
};
public static int pixelsToDp(int px, DisplayMetrics metrics)
{
return (int) (px / metrics.density);
}
Now when I run the app I have following log: E/TEST: heightInPx:96, heightInDp:32, maxDisplayDriver:0
I believe the height is wrong because I can put at least 4 items in my screen that will be 240dp, while the log shows something else.
You can get the height of listview this way because a listview can have n number of childs and getHeight() returns the actual height not the height of currently visible portion. So for listview and other similar view it doesn't return height as even system doesn't know because all childitems are not yet rendered.
So as suggested by other geeks as well, you need to calculate the height of item + margin+padding (if any) and then multiply it by number of child(how do you know this? read ahead).
Now number of child would be different in different screen size. So you need to put a counter inside getView to check how many times its getting called onCreate to get to know the number of childitem
I'm developing a custom keyboard using Xamarin.
My keyboard view has an overridden OnDraw() for both the view container itself and it's child key views. I'm also using SetWillNotDraw(false) appropriately for each view. It currently works beautifully in 5.0.1 on my Nexus 10 tablet.
In Android 6.0.1, on a Nexus 6 and a Nexus 6P, the keyboard view correctly draws itself (just a background color). The child key views however are never drawn, even if I iterate through the view hierarchy and force an invalidate on each one. This seems to be specific to Marshmallow.
I don't know if there's something new I need to account for in this version of Android or if I'm encountering a bug.
Any help or suggestions are welcome.
Code:
KeyboardView
KeyView
Some extra details to shed light on the original post:
The three major files we use for keyboard rendering are KeyboardView.cs, KeyboardRowView.cs, and KeyView.cs.
KeyboardView (the container for the whole keyboard)
This has no trouble rendering. KeyboardView extends a LinearLayout and its OnDraw method runs, calling a Build() function to create what it needs (just a basic background which will "hold" the individual keys):
protected override void OnDraw(Canvas canvas)
{
Build();
base.OnDraw(canvas);
// background
Paint bg = new Paint(PaintFlags.AntiAlias);
bg.Color = BG; // light blue
canvas.DrawRect(0, 0, MeasuredWidth, Height, bg);
InvalidateKeys();
}
(...and Build() below...)
public void Build()
{
// only build once
if (keyLayout != null)
return;
// clear out children
RemoveAllViews();
// define sizes of stuff
if (isPortrait)
{
keyMargin = (int)(MeasuredWidth * .01f);
}
else
{
keyMargin = (int)(MeasuredHeight * .01f);
}
keyWidth = (MeasuredWidth - (keyMargin * 2)) / keyboard.MaxCols;
keyHeight = (MeasuredHeight - (keyMargin * 2)) / keyboard.Rows.Count;
// set general padding around keyboardview
SetPadding(keyMargin, keyMargin, keyMargin, keyMargin);
// build KeyLayout from the keyboard object
keyLayout = new List<List<KeyView>>();
int idx = 0;
foreach (List<Key> row in keyboard.Rows)
{
keyLayout.Add(new List<KeyView>());
// create and add new KeyboardRowView
KeyboardRowView krv = new KeyboardRowView(Context, this, idx);
AddView(krv);
// figure out if we need a margin offset for this row
int extraMargin = 0;
int numCols = CountRowCols(row);
if (numCols < keyboard.MaxCols)
{
// measure full width of the button container and the total row margin
int rowWidth = (int)(numCols * keyWidth);
int rowMargin = MeasuredWidth - (keyMargin * 2) - rowWidth;
// add the offset
extraMargin = rowMargin / 2;
}
// build keys and add them to keyLayout and KeyboardRowView
int idx2 = 0;
foreach (Key key in row)
{
int leftMargin = idx2 == 0 ? extraMargin : 0;
KeyView kv = new KeyView(Context, this, key, leftMargin);
keyLayout[idx].Add(kv);
krv.AddView(kv);
idx2++;
}
idx++;
}
}
(As a friendly reminder, we're doing this because we need a custom keyboard which can only display certain keys/commands to our users.)
KeyboardRowView (the container for each row of keys)
This also extends a LinearLayout, and also has its OnDraw method called:
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
paint.SetARGB(255, 0, 0, 0);
paint.SetStyle(Paint.Style.Stroke);
paint.StrokeWidth = 3;
canvas.DrawRGB(255, 255, 255);
canvas.DrawRect(0, 0, 100, 100, paint);
}
KeyView (the class which loads and renders each individual key)
KeyView extends View and View.IOnTouchListener. KeyView's constructor is called, but its OnDraw method is never called/executed:
// key views are always dynamically created
public KeyView(Context ctx, KeyboardView parent, Key k, int leftMargin)
: base(ctx)
{
// make sure the key will draw
SetWillNotDraw(false);
keyboard = parent;
key = k;
isDown = false;
// check for an overridden span to adjust width, if needed
int span = string.IsNullOrEmpty(key.Span) ? 1 : Convert.ToInt32(key.Span);
int keyWidth = keyboard.keyWidth + ((span - 1) * keyboard.keyWidth);
width = keyWidth;
height = keyboard.keyHeight;
// set margin
var parameters = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WrapContent,
LinearLayout.LayoutParams.MatchParent
);
parameters.LeftMargin = leftMargin;
LayoutParameters = parameters;
// set touch listener
SetOnTouchListener(this);
// enable haptic feedback for button presses
HapticFeedbackEnabled = true;
}
(...and OnDraw)
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
KeyState primary = key.Primary;
KeyState secondary = key.Secondary;
if (keyboard.swapped)
{
primary = key.Secondary != null ? key.Secondary : key.Primary;
secondary = key.Secondary != null ? key.Primary : null;
}
if (keyboard.shifted)
{
if (primary.Shift != null)
primary = primary.Shift;
if (secondary != null && secondary.Shift != null)
secondary = secondary.Shift;
}
// figure out what color the key is supposed to be
Paint bg = new Paint(PaintFlags.AntiAlias);
bg.Color = GetKeyBgColor(key.Style);
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Lollipop)
canvas.DrawRoundRect(keyboard.keyMargin, keyboard.keyMargin, width - keyboard.keyMargin, height - keyboard.keyMargin, keyboard.keyMargin, keyboard.keyMargin, bg);
else
canvas.DrawRoundRect(new RectF(keyboard.keyMargin, keyboard.keyMargin, width - keyboard.keyMargin, height - keyboard.keyMargin), keyboard.keyMargin, keyboard.keyMargin, bg);
// draw primary key state
Paint fg = new Paint(PaintFlags.AntiAlias);
fg.TextSize = height * .5f;
fg.Color = GetKeyFgColor(key.Style);
string character = string.IsNullOrEmpty(primary.Character) ? "#" : primary.Character;
int charWidth = Convert.ToInt32(fg.MeasureText(character));
int charX = (width - charWidth) / 2;
canvas.DrawText(character, charX, (height * .7f), fg);
// draw secondary key state
if (secondary != null)
{
fg.TextSize = height * .25f;
fg.Color = GetKeyFgColor(key.Style, true);
character = string.IsNullOrEmpty(secondary.Character) ? "#" : secondary.Character;
charWidth = Convert.ToInt32(fg.MeasureText(character));
charX = width - charWidth - (keyboard.keyMargin * 2);
canvas.DrawText(character, charX, (height * .35f), fg);
}
}
I am confused. Both KeyboardView and KeyboardRowView have a SetWillNotDraw(false); function call in their constructor/initialization methods. KeyView also has the same function call, and successfully receives each key value that needs to be rendered. What I don't get is why it just...won't...draw...the...keyboard. (Argh.) When I spoke with the original poster about this, he told me that all the conditions have been met in order for the keyboard keys to be rendered. I tried attaching breakpoints to see what was preventing KeyView's OnDraw from being called, but got caught up in repeated OnMeasure function calls (and there are a lot of keys that get rendered, so that got old quick).
It's worth mentioning that we've tested it on the latest Nexus 6P smartphone (running stock Android 6.0 Marshmallow) and an old Motorola Droid 4 (with Marshmallow installed via CyanogenMod 13). When we tried it using a Xamarin Android Player emulator (running Marshmallow), it actually worked... my guess is that the emulator might be rendering the keyboard with no problem because the actual phones themselves are either
(a) restricting access somehow
(b) potentially holding on to old code and we just haven't completely removed their old .apks
(c) some other issue I haven't thought of
Thank you for your time. If anyone can think of a possible direction to go in, it would be appreciated!
Fixed by properly implementing OnMeasure and OnLayout for each custom view through the top-down approach suggested by Android documentation.
I have created two layout vertically of equal width. And I have string data to be displayed on text view dynamically. When string is greater than the width of the layout then string is wrapped to the width of the layout and for remaining string I want to create a new TV dynamically. This process ends till remaining string finishes. For next string same process continues. When the process reaches bottom of linearlayout1, remaining string should starts from linearlayout2. And the process continues till it reaches bottom of linearlayout2.
I tried like this
private void nextlinechar(int numChars,String devstr) {
nextchar=devstr;
Log.d("char 1",""+nextchar);
TextView sub=new TextView(getApplicationContext());
sub.setLines(1);
sub.setTextColor(Color.BLACK);
sub.setTextSize(textsize);
sub.setText(nextchar);
nextchar=devstr.substring(nextcharstart);
String textToBeSplit = nextchar; // Text you want to split between TextViews
String data=TextMeasure(nextchar,sub);
float myTextSize=sub.getTextSize();
float textView2Width=400;
// String next=TextMeasure(nextchar,sub);
Paint paint = new Paint();
paint.setTextSize(myTextSize); // Your text size
numChars1= paint.breakText(textToBeSplit, true,textView2Width, null);
nextchar1=nextchar.substring(numChars1);
// Log.d("char",""+i+" "+nextchar.length());
main.addView(sub);
nextlinechar(numChars1,nextchar);
}
Illustration
Possible Solution 1
What you need is a FlowLayout, found here. Basically the text needs to wrap around, instead of overflow to the right.
Possible Solution 2
Try to use a webview instead, and populate the text in 2 webviews. That will be faster with lesser code and not as buggy.
I used FlowLayout only when I needed to click on each word separately. Basically a test for grammar where people select the Parts Of Speech of the sentence. For that I needed listener on each word.
Reffer This may be this help you:
package com.example.demo;
import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialise();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialise();
}
private void initialise() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
// max size defaults to the initially specified text size unless it is
// too small
}
/*
* Re size the font so the specified text fits in the text box assuming the
* text box is the specified width.
*/
private void refitText(String text, int textWidth) {
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft()
- this.getPaddingRight();
float hi = 100;
float lo = 2;
final float threshold = 0.5f; // How close we have to be
mTestPaint.set(this.getPaint());
while ((hi - lo) > threshold) {
float size = (hi + lo) / 2;
mTestPaint.setTextSize(size);
if (mTestPaint.measureText(text) >= targetWidth)
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, height);
}
#Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after) {
refitText(text.toString(), this.getWidth());
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw) {
refitText(this.getText().toString(), w);
}
}
// Attributes
private Paint mTestPaint;
}
The following is an idea I came up with (of course I first tried to search for a built-in method of TextViwe which does what you need, but couldn't find any):
Determine how many lines should be displayed in each of the layouts (left and right) depending on the screen size (this.getResources().getDefaultDisplay() and then search for the right method), the desired text size and the spaces between the layouts and between the text and the top/bottom edges. Don't forget you may need to convert pixels to dips or vice versa, as most Android size measure methods return size in pixels.
Set the max number of lines to each of the TVs (tv.setLines()).
Set the entire text to the left TV.
Now the first half of the text will be displayed in the left TV. The other part of the text will be hidden because you've reached the max num of lines in the TV. So, use getText() to receive the text currently displayed in the TV (hopefully, this will return only the first half of the text.. haven't tried that out yet, sorry. If not, maybe there's some getDisplayedText() method or something). Now you can use simple Java methods to retrieve only the remining part of the text from the original text (a substring) and set it to the second TV.
Hope that helps.
I would like to create a list of about 200 ImageViews (random heights) with the following layout in a 'collage' fashion:
Normally I would do this in a ListView for the peformance gained by using Adapters but since i want the images to be displayed in columns, and with different height (See picture Example ) depending on the pictures, I cannot use a single listview for this purpose.
I have tried implementing this layout with:
Three ListViews with synchronized scrolling = Slow
Single ListView with each row containing three images = Not allowing different heights
GridView = Not allowing different heights
GridLayout = Difficult to implement different heights programmatically. Because of no adapter, OutOfMemoryErrors are common
FlowLayout = Because of no adapter, OutOfMemoryErrors are common
ScrollView with three Vertical LinearLayouts = Best solution so far, but OutOfMemoryErrors are common
I have ended up using three LinearLayouts in a ScrollView, but this is far from optimal. I would rather use something with an Adapter.
EDIT
I have been looking at the StaggeredGridView, as in a response below, but I find it quite buggy. Are there any implementations of this that are more stable?
I think I have a working solution for you.
The main files mentioned here are also on PasteBin at http://pastebin.com/u/morganbelford
I basically implemented a simplified equivalent of the github project mentioned, https://github.com/maurycyw/StaggeredGridView, using a set of excellent LoopJ SmartImageViews.
My solution is not nearly as generic and flexible as the StaggeredGridView, but seems to work well, and quickly. One big difference functionally is that we layout the images always just left to right, then left to right again. We don't try to put the next image in the shortest column. This makes the bottom of the view a little more uneven, but generates less shifting around during initial load from the web.
There are three main classes, a custom StagScrollView, which contains a custom StagLayout (subclassed FrameLayout), which manages a set of ImageInfo data objects.
Here is our layout, stag_layout.xml (the 1000dp initial height is irrelevant, since it will get recomputed in code based on the image sizes):
// 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>
Here is our main Activity's onCreate, which uses the layout. The StagActivity just basically tells the StagLayout what urls to use, what the margin should be between each image, and how many columns there are. For more modularity, we could have passed these params to the StagScrollView (which contains the StagLayout, but the the scroll view would have just had to pass them down the layout anyway):
// 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
Before we get to the meat of the solution, here is our simple StagScrollView subclass. His only special behavior is to tell his main child (our StagLayout) which the currently visible area is, so that he can efficiently use the smallest possible number of realized subviews.
// 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());
}
Here then is the most important class StagLayout.
First, setUrls sets up our data structures.
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
}
Our main data structure is ImageInfo. It is a kind of lightweight placeholder that allows us to keep track of where each image is going to be displayed, when it needs to be. When we layout our child views, we will use the information in the ImageInfo to figure out where to put the actual view. A good way to think about ImageInfo is as a "virtual image view".
See comments inline for details.
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;
}
}
The rest of the magic happens in StagLayout's onMeasure and 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);
}
}
Ok, now let's see how we actually arrange all the ImageInfos.
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;
}
And, now let's see how we create, resuse and dispose of real SmartImageViews during onLayout.
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;
}
Remember that _viewportTop and _viewportBottom are set every time the user scrolls.
// 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();
}
You can have a look at https://github.com/maurycyw/StaggeredGridView
I have not worked with it personally, but you could atleast steal some concepts.
Create a list view in a layout.
Create another layout with same background as that of list view background layout with three Image Views (next to each other ie to the right of each other) with their properties set to Wrap_Content horizontally and the whole Views properties in which image views are put to Wrap_Content.
Inflate the layout in the getview() method of listview adapter. In this you need to set 3 set of images in Image Views of the inflated Layout.
Hope this helps!
I guess it can be implemented with three independent list view, only thing which you have to do it to inflate layout for imageview and add it to listview.
use following as layout parameters during inflation.
Layout Width : match_parent
layout Height: wrap_content
you can assign layout weight as .3 for all the three list view with layout_width as 0dp and height as fill_parent.
hope this helps.
Can't you use your current solution wrapped in a custom list ?
in getView method for each row inflate your existing solution (checking converview ofcourse)
i.e. ScrollView with three Vertical LinearLayouts.
Do you know why the 3 List View solution was slow?
How many different sizes are in each column? I think that for the recycling of views to be efficient, you would want to create a view type for each size of image, and then make sure that you use getItemViewType, to be sure that you're recycling the correct type of view. Otherwise, you will not get much benefit from the recycling. You would want to be able to just reset the source for the image view.