I am new to Android development, but familiar with the concept of views, controls, objects, XML layouts, C#, etc.
I'm trying to create a horizontally scrolling "list" of images using as much native functionality as possible. (I'm not adverse to using custom components, but I'm trying to learn and optimize as much as possible before hacking something together.)
I currently have a Gallery with an adapter tied to it. The adapter is creating ImageViews as seen in many basic tutorials. On each pass of the adapter, I'm setting the background image for the ImageView. My hope was that I'd be able to position the foreground image to lay on top of the background image at a specific X/Y position. Unfortunately, I haven't made it past the point of getting the background image to behave the way I'd like it to.
Is this even possible with a simple Gallery and an ImageView? Or, do I need to build a custom control of some sort (possibly using nested layouts?) and use that control on each iteration of the adapter?
Any help will be greatly appreciated.
Here's what I'm seeing...
http://philaphan.com/public/stackoverflow/gallery1.png
...and what I'd like to see...
http://philaphan.com/public/stackoverflow/gallery2.png
Here's my code:
public class MyAdapter extends BaseAdapter
{
private Context mContext;
public MyAdapter(Context c)
{
mContext = c;
}
public int getCount()
{
return App.myList.size();
}
public Object getItem(int position)
{
return position;
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
String imagePath = App.myList.get(position).thumbnail;
ImageView i = new ImageView(mContext);
i.setLayoutParams(new Gallery.LayoutParams(150, 150));
i.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
i.setBackgroundResource(R.drawable.image_bk5);
//i.setBackgroundColor(Color.BLACK);
File f = new File(imagePath);
if (!f.exists())
{
i.setImageResource(R.drawable.image_missing);
}
else
{
Bitmap bmp = BitmapFactory.decodeFile(imagePath);
i.setImageBitmap(bmp);
}
return i;
}
}
I think you can use:
i.setPadding(50, 50, 50, 50); //setpadding(left,top,right,bottom)
Try to use this..
replace i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); with i.setScaleType(ImageView.ScaleType.FIT_XY);
or check your background image . I think your background image width creating problem.
I was able to work around this by using a HorizontalScrollView with a LinearLayout nested within it. I then programmatically add two ImageViews -- one for the background and one for the foreground -- and I position the foreground at whatever X/Y that I choose.
I'd still be interested in knowing if this can be done within a Gallery control, if anyone sees this at a later date.
As a general rule, if you want to add a background image to any kind of inflated gallery or ArrayAdapter or BaseAdapter, then - Make sure that the background image is defined in the parent layout, or parent view, for example the main layout, using the attribute "android:background="#drawable/backupimage".
Do not set the background in the XML representing the custom-row/object-view of the repeating instances.
This way the background image will be displayed only once, appearing static behind all inflated objects, and not duplicated again and again on every single row/object.
Related
I have a RecyclerView holding a list of Artists. An artist can be marked as a favorite or not a favorite.
There's a heart to represent this state, and I have a transition drawable, where I'm going from a outlined heart, to a filled in heart based on if the artist in question has been marked as a favorite. The animation itself is working correctly and smoothly transitions between the two when the user clicks on the heart, and looks beautiful.
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
ContextCompat.getDrawable(getActivity(), R.drawable.favorite_border),
ContextCompat.getDrawable(getActivity(), R.drawable.favorite)});
artistViewHolder.image.setImageDrawable(td);
if (artist.isFavorite()) {
td.startTransition(0);
}
In the onClick for the item, I call either .reverseTransition(300) or .startTransition(300) depending on the next state.
I'm running into problems anytime the view is created (1st screen load) and I need to start in the 2nd position (favorite). The initial load you can see it flicker from the empty heart to the filled heart, even though my animation time is set to 0 when it's created. This also happens anytime the list gets invalidated such as if it gets filtered into a smaller set of artists.
I guess since it needs to do things and isn't actually just setting the drawable, 0 isn't actually "instant". I can't find any other way to start in the "end" position though, other than to reverse the starting position of the images in the transition drawable itself.
If I do that though, start/reverse are going to behave differently on a heart by heart basis. At best, I would need to extend the transition drawable, and override the start/reverse/reset methods to take into account which initial state it's in, and that seems kind of hacky?
Am I missing something obvious to start in the end position but not cause that flicker?
Thanks!
You don't migtht remove flicker on td.startTransition(0);
So, you need change order in your Drawables array.
For example
Drawable favorite_border = ContextCompat.getDrawable(getActivity(), R.drawable.favorite_border);
Drawable favorite = ContextCompat.getDrawable(getActivity(), R.drawable.favorite);
TransitionDrawable td = new TransitionDrawable(isFavorite
? new Drawable[]{favorite, favorite_border}
: new Drawable[]{favorite_border, favorite});
artistViewHolder.image.setImageDrawable(td);
This will work provided that you do this in onBindViewHolder
Unless there's a better way to do this, I've gone ahead and extended it, and flipped the start/reverse call based off the initial state.
public class MyTransitionDrawable extends TransitionDrawable{
private boolean initialFavorite = false;
public MyTransitionDrawable(Drawable[] layers) {
super(layers);
}
public MyTransitionDrawable(Drawable[] layers, boolean initialFavorite) {
super(layers);
this.initialFavorite = initialFavorite;
}
public boolean isInitialFavorite() {
return initialFavorite;
}
#Override
public void startTransition(int durationMillis) {
if(!initialFavorite){
super.startTransition(durationMillis);
}else{
super.reverseTransition(durationMillis);
}
}
#Override
public void reverseTransition(int durationMillis) {
if(!initialFavorite){
super.reverseTransition(durationMillis);
}else{
super.startTransition(durationMillis);
}
}
}
`
Recently, I started using Google ImageWorker class for loading bitmaps on a background thread. This class handles everything, including putting the bitmaps in the ImageView. Google talks about this class (and it's helper classes for Image manipulation and caching) here: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
The ImageView(s) in my case are parts of list items in a ListView. Here's the interesting part of getView on my ArrayAdapter:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView == null ? inflater.inflate(R.layout.nodelist_item, null) : convertView;
NodeHolder holder = getHolder(view);
Node node = mList.get(position);
holder.txtTitle.setText(node.Title);
//Setting the rest of the different fields ...
//And finally loading the image
if(node.hasArt())
worker.loadImage(node.ImageID, holder.imgIcon);
}
The entire getView method can be seen here: http://pastebin.com/CJbtVfij
The worker object is a very simple implementation of the ImageWorker:
public class NodeArtWorker extends ImageWorker {
protected int width;
protected int height;
public NodeArtWorker(Context context) {
super(context);
addImageCache(context);
//Code for getting the approximate width and height of the ImageView, in order to scale to save memory.
}
#Override
protected Bitmap processBitmap(Object data) {
Bitmap b = getBitmap(data);
if(b == null) return null;
return Utils.resizeBitmap(b, width, height);
}
protected Bitmap getBitmap(Object data) {
//Downloads the bitmap from a server, and returns it.
}
}
This works very well, and the performance is much better now than before. However, if I change the ImageID on some of the items in the list, and call adapter.notifyDataSetChanged() to rebuild the view (and thereby start loading new bitmaps), I get a RuntimeException:
0 java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap#41adf448
1 at android.graphics.Canvas.throwIfRecycled(Canvas.java:1058)
2 at android.graphics.Canvas.drawBitmap(Canvas.java:1159)
3 at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:440)
4 at android.widget.ImageView.onDraw(ImageView.java:1025)
5 at android.view.View.draw(View.java:14126)
The full stacktrace can be seen in the following pastebin entry, but is probably uninteresting, as it is the typical Android view hierarchy redraw stacktrace: http://pastebin.com/DsWcidqw
I get that the Bitmaps are being recycled, but I don't understand exactly where and what can be done about it. As my code comes directly from Google, and is, as far as I understand, the recommended way of doing this, I am very confused.
On Android, Bitmaps can be recycled to be later re-used (much faster than re-creating a Bitmap).
The Bitmap#recycle()method will flag the Bitmap as recycled.
So if you try to set such a recycled bitmap to an ImageView or draw it on a Canvas, you will end up with this exception:
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap
The official demo that you linked on your question is dealing with recycled Bitmaps.
It uses a dedicated method hasValidBitmap() and checks the Bitmap#isRecycled() value:
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
So you need to do the same. When you're searching on your cache for a Bitmap, before applying it to an ImageView, check if it's recycled or not. If it's not you can directly set it, otherwise, you need to update the Bitmap.
To create a new Bitmap from a recycled Bitmap, you can use the Bitmap::createBitmap(Bitmap) method.
You can find more details on this page.
i think this is not the ImageWorker class bug, this is
com.mobeta.android.dslv.DragSortListView.dispatchDraw(DragSortListView.java:797)
issue. so in order to see my answer is correct or not just remove that library and see if it works or not. after that i think the best solutions are:
1- not use that library.
2- report issue on github.
3- solve it by your own and debugge it.
4- or you can set null for image holder bitmap
holder.imgIcon.setBitmapDrawble(null);
and then call worker.loadImage.
I basically made a class, with this method:
void setResource(Context context, R.id resourceID) {
ImageView test = (ImageView) context.findViewById(resourceID);
}
The "findViewById" gives me a can't resolve method.
I have three ImageViews defined in XML, I would like to set the image associated to them programatically. in my main_activty I do so by using:
ImageView blah = (ImageView) this.findViewById(R.id.blah);
Then:
blah.setImageResource(R.drawable.pony);
HOWEVER, I would like to pass everything to my class I made and have it handle all that in the custom class, but when I try to pass everything (the code at the very top; "setResource") I get that findViewByID can't resolve method. Is there a way to do it that way?
I think you are trying to find the words to express what you are trying to do, and with some of the basic concepts. This is my best guess of what you are trying to accomplish...
The code below takes an id for drawable, loads it, and then puts it into an imageView in your layout. The ImageView must already be there in your layout, it does not create one for you dynamically.
void setResource(Context context, int resourceId) {
//resourceId = R.drawable.ponies (example)
Drawable drawable = context.getResources().getDrawable(resourceId);
//R.id.main_imageview is example imageView in a layout.
ImageView iv = findViewById(R.id.main_imageview);
iv.setImageDrawable(drawable);
}
i am retriving around 20 images url from an xml , and appearing them in gridview.
thing going nice except one.
when i scroll down new images start loading , but when i again scrolls up. the previously loaded images again stars loading .
how can i resolve this bug. the previously loaded image should be retained in the layout , why it is not retaining itself .
I am using the adapter extends the BaseAdapter to view the images
Gridview like any other views that inherit AdapterView only uses the amount of views that you see on the screen. So when you scroll your views going to be reused. The Adapter's responsibility is to reassign the content to the views.
So, you need to cache your bitmaps. There is a very nice tutorial about this on the Android developer side.
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
(I uses grid view in the example)
Every time you scroll, getView() method of the adapter is called.
So every time one view come in to the screen is built in the getView() method.
You build the new view in that method, I suppose you download the image when that method is called, then one solution is caching the images. If you download few images and they are small size, a simply Map with key url and value bitmap solves the problem, if not, use database.
Every time getView() is called, check first if image is in cache, if image is in cache use it to build the view, if not download it.
use LoaderImageView from this blog http://blog.blundell-apps.com/imageview-with-loading-spinner/ & add following function
public void setImageDrawable(final MyGridViewItem obj) {
mDrawable = null;
if(obj.mDrawable != null){
mDrawable = obj.mDrawable
imageLoadedHandler.sendEmptyMessage(COMPLETE);
return;
}
mSpinner.setVisibility(View.VISIBLE);
mImage.setVisibility(View.GONE);
new Thread(){
public void run() {
try {
mDrawable = getDrawableFromUrl(imageUrl);
obj.mDrawable = mDrawable;
imageLoadedHandler.sendEmptyMessage(COMPLETE);
} catch (MalformedURLException e) {
imageLoadedHandler.sendEmptyMessage(FAILED);
} catch (IOException e) {
imageLoadedHandler.sendEmptyMessage(FAILED);
}
};
}.start();
}
I have a fullscreen view flipper on the galaxy tab containing two relative layouts, each of which is displaying between five and ten image views.
I am having problems when I try to slide one of the view flipper viewso ut and the other in, the "animation" does nothing and then just switches.
If I remove all but one of the image views in each of the view flipper children the animation is fine.
Can I somehow force it to use a mixed down version of the image for the animation, or is the relative layout container causing the performance problem?
There should be nothing going on at the time the flipper animation occurs so I can't understand the problem.
Thanks
EDIT: Some of the problematic code: Perhaps this might indicate areas causing the performance problems?
I have in my onClick, two method calls, used for switching backwards through the views and forward. A view is "generated" immediately before being added to the flipper and switched.
so, on the click of the back button (for example) I have (amongst other things) ....
nextView = new RelativeLayout(this);
makeAView(selectedViewObjects, viewHolder2);
switchViewNow(-1);
currentView = nextView;
...
private void makeAView(ViewObjects vObjects, RelativeLayout nextView)
{
for(Object obj : vObjects.viewObjects)
{
if(obj instanceof Image)
{
addImageView(nextView, obj);
{
else
{
if(obj instanceof Anim)
{
addAnimView(nextView, obj);
}
}
Where Image is a custom View implementation for displaying a static image and Anim is a custom view implementation for displaying an AnimationDrawable (multiframe image cycle).
Next we have the switch method:
private void switchViewNow(int direction)
{
if(direction == -1)
{
viewFrame.setInAnimation(AnimClass.inFromLeftAnimation(null));
viewFrame.setOutAnimation(AnimClass.outToRightAnimation(null));
viewFrame.showNext();
}
else
if(direction == 1)
{
viewFrame.setInAnimation(AnimClass.inFromRightAnimation(null));
viewFrame.setOutAnimation(AnimClass.outToLeftAnimation(null));
viewFrame.showNext();
}
}
I could be wrong, but it looks to me like you have overloaded nextView in a confusing way.
It is defined outside of makeAView, but makeAView expects a parameter which it names nextView.
You are passing viewHolder2 as nextView to this function.
This means that makeAView will NOT change the 'global' nextView, it will change viewHolder2.
But then you seem to assume that the 'global' nextView has been modified because you use it to assign currentView.
I think it would be a good place to start if you assign unique names to these two versions of nextView.
I wonder if you are even still looking for a solution: this post is rather old and ignored.