So, I've been doing xml layout for a project that involves getting a horizontal scrollable row of images on a screen, and did so using just a horizontalscrollview. and a bunch of imagebuttons. I used an include to put this on another xml layout page and another programmer will then populate the images dynamically.
My question is, how would the gallery control benefit us? I haven't done much Java programming and I've seen some instruction online of how to implement this control, but not a lot on WHY you would use this. It looks like this control works mainly via Java insertion via array, but other than that I can't tell what the benefits are from reading over my way of just creating the layout and having this other programmer insert his own images manually.
Another related question - do these images for a gallery need to me imageviews, or can they be imagemaps? Currently they are imagemaps because we want them to be clicable to go to a user's profile, etc.
Thanks!
Gallery is nearly perfect. In one of my projects I do have a LinearLayout with a Gallery in it:
<Gallery
android:id="#+id/gallery"
android:layout_height="0dip"
android:layout_weight="1"
android:layout_width="fill_parent"
android:spacing="2dip" />
An activity implements OnItemClickListener:
public class MyActivity extends Activity implements OnItemClickListener {
A data structure contains all items and is send to an adapter:
private void processGallery() {
adapter = new MyAdapter(this, containers, appName);
if (adapter != null) {
gallery.setAdapter(adapter);
}
}
#Override
public void onItemClick(final AdapterView<?> adapterView, final View view, final int position, final long id) {
if (containers != null) {
container = containers.get(position);
if (container != null) {
// Handle selected image
}
}
}
The adapter is a usual BaseAdapter - nothing magic:
public class MyAdapter extends BaseAdapter {
private ArrayList<Container> containers;
private Context context;
public int getCount() {
return containers.size();
}
public Object getItem(final int position) {
return containers.get(position);
}
public long getItemId(final int position) {
return position;
}
public View getView(final int position, final View contentView, final ViewGroup viewGroup) {
ImageView imageView = new ImageView(context);
Container container = containers.get(position);
if (container != null) {
// Do your image thing here
}
return imageView;
}
public MyAdapter(final Context context, final ArrayList<Container> containers, final String appName) {
this.context = context;
this.containers = containers;
}
}
This simple code gives a horizontal scrolling image gallery with clickable items. The click is send to the activity - no need to do something fancy in the adapter. I removed from the code shown here a DrawableCache that I use because my items do come from the web.
Related
I am getting a very strange bug in my application. To make it more clear I will create a similar example with minus code.
I am reading objects from Firebase Realtime Database with an addListenerForSingleValueEvent. While I am reading the objects, I am stored them in an Array that I passed to an Adapter in a Recycleview. At this point, I can say, after debugging, that all seems to work correctly.
Then in the Adapter, I have a code similar to this:
public class AdapterObject extends RecyclerView.Adapter<AdapterObject.ViewHolder> {
ArrayList<Object> objectList;
Context mContext;
public AdapterObject (Context context, ArrayList<Object> objectList){
this.mContext = context;
this.objectList = objectList;
}
#NonNull
#Override
public AdapterObject.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.object_grid_layout, parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull AdapterObject.ViewHolder holder, int position) {
Object o = objectList.get(position);
Log.d("TAG", o.getAtribute());
if (o.getAtribute().equals("A")){
holder.atribute.setVisibility(View.VISIBLE);
}
#Override
public int getItemCount() {
return objectList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView atribute;
boolean favorite;
String descuento, precioOriginal;
public ViewHolder(#NonNull View itemView) {
super(itemView);
atribute = itemView.findViewById(R.id.atribute);
}
}
}
As you can see in the code if the current object has the attribute value == "A", then his Textview is displayed, otherwise, the Textview remains hidden.
All seems correct when I debug it because the objects and their attribute corresponds to the Database, but when I deploy the application in the Android simulator and I start going up and down on the Recycleview, the holders start to display the Textviews although the console debugs seems correct...
Is this normal in RecycleViews? How can I fix that? I have found this, do you think it has any relation?
This is an extract from the RecyclerView documentation
As the name implies, RecyclerView recycles those individual elements.
When an item scrolls off the screen, RecyclerView doesn't destroy its
view. Instead, RecyclerView reuses the view for new items that have
scrolled onscreen. This reuse vastly improves performance, improving
your app's responsiveness and reducing power consumption.
That means that when the view gets reused it will keep the current properties. It's up to you to change them when onBindViewHolder gets called.
In your specific case
#Override
public void onBindViewHolder(#NonNull AdapterObject.ViewHolder holder, int position) {
Object o = objectList.get(position);
Log.d("TAG", o.getAtribute());
if (o.getAtribute().equals("A")){
holder.atribute.setVisibility(View.VISIBLE);
} else. {
holder.atribute.setVisibility(View.GONE);
}
}
I have created an app that contains a viewPager inside the mainActivity, this viewPager contains 5 fragments, of which 4 are recyclerViews and one is a normal linearLayout containing some text...
Here is a screenshot of the app, not all tabs in the tablayout are visible:
Now, as you might have seen already, there isn't much space in the viewPager for the user to see anything, so they have to scroll too much to view things in the recylerView. I want to modify my app so that when the user tries to scroll inside the recyclerView, the visible part of the mainActivity is scrolled down till the recyclerView occupies the entire page and then the recyclerView begins to scroll normally.
Can someone please help me implement this type of scroll feature into my app. You can just check out this app for a reference to what I'm saying. Just open up any movie or tvSeries and then try scrolling, the mainActivity gets scrolled first and then the rest of the layout.... Can someone please help. I've already tried the solutions on stackOverflow and many of them don't work, I also tried to google for a solution, but didn't get anything useful....
Here is the code for my adapter:
public class cardViewAdapterCreditsCast extends RecyclerView.Adapter<cardViewAdapterCreditsCast.ViewHolder> {
private Context context;
private List<creditsModel> creditsModels;
public cardViewAdapterCreditsCast(Context context, List<creditsModel> creditsModels) {
this.context = context;
this.creditsModels = creditsModels;
}
#Override
public cardViewAdapterCreditsCast.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.new_cast_row, parent, false);
return new cardViewAdapterCreditsCast.ViewHolder(view);
}
#Override
public void onBindViewHolder(cardViewAdapterCreditsCast.ViewHolder holder, int position) {
final creditsModel creditsModel = creditsModels.get(position);
int tempNumber = holder.celebImage.getWidth();
holder.celebImage.setMinimumHeight(tempNumber);
holder.celebImage.setMaxHeight(tempNumber + 1);
String imagePath = "https://image.tmdb.org/t/p/w500" + creditsModel.getProfilePath();
holder.starringAs.setText(creditsModel.getCharacter());
holder.celebName.setText(creditsModel.getActorName());
if (creditsModel.getProfilePath() != null) {
Picasso.with(context).load(imagePath).transform(new CircleTransform()).into(holder.celebImage);
} else
holder.celebImage.setBackgroundResource(R.drawable.not_found);
}
#Override
public int getItemCount() {
return creditsModels.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView celebImage;
public TextView celebName;
public TextView starringAs;
private LinearLayout linearLayout;
public ViewHolder(View itemView) {
super(itemView);
linearLayout = (LinearLayout) itemView.findViewById(R.id.castRowMainLinearLayout);
celebImage = (ImageView) itemView.findViewById(R.id.castRowImage);
celebName = (TextView) itemView.findViewById(R.id.castRowName);
starringAs = (TextView) itemView.findViewById(R.id.castRowAppearance);
}
}
}
Use nested Scrollview if there are more than one scrollview or recyclerview.
You can use a CoordinatorLayout to implement this easily.
Example, http://saulmm.github.io/mastering-coordinator
Documentation, https://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.html
I created a custom listview layout with images which are loaded from web like this:
http://i.stack.imgur.com/l8ZOc.png
It works fine when scrolling down. However, when you scroll down, the previous items go out of screen then destroyed. When you try to scroll up again, it gets loaded again (from cache, faster but not instant) which causes a delay and it is not fluent as it should be.
1.Is there an example of how to do this properly?
2.Is there a way to prevent listview items being destroyed when they are out of screen?
3.If so, will it cause problems to keep too many items?
Bellow is my code:
MenuAdapter:
public class MenuAdapter extends BaseAdapter{
Context context;
List<MyMenuItem> menuItems;
MenuAdapter(Context context, List<MyMenuItem> menuItems) {
this.context = context;
this.menuItems = menuItems;
}
#Override
public int getCount() {
return menuItems.size();
}
#Override
public Object getItem(int position) {
return menuItems.get(position);
}
#Override
public long getItemId(int position) {
return menuItems.indexOf(getItem(position));
}
private class ViewHolder {
ImageView ivMenu;
TextView tvMenuHeader;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.menu_item, null);
holder = new ViewHolder();
holder.tvMenuHeader = (TextView) convertView.findViewById(R.id.tvMenuHeader);
holder.ivMenu = (ImageView) convertView.findViewById(R.id.ivMenuItem);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
MyMenuItem row_pos = menuItems.get(position);
Picasso.with(context)
.load(row_pos.getItem_image_url())
.into(holder.ivMenu);
holder.tvMenuHeader.setText(row_pos.getItem_header());
Log.e("Test", "headers:" + row_pos.getItem_header());
return convertView;
}
}
MyMenuItem:
public class MyMenuItem {
private String item_header;
private String item_image_url;
public MyMenuItem(String item_header, String item_image_url){
this.item_header=item_header;
this.item_image_url=item_image_url;
}
public String getItem_header(){
return item_header;
}
public void setItem_header(String item_header){
this.item_header=item_header;
}
public String getItem_image_url(){
return item_image_url;
}
public void setItem_image_url(String item_image_url){
this.item_image_url=item_image_url;
}
}
MainActivity:
public class MyActivity extends Activity implements AdapterView.OnItemClickListener {
List<MyMenuItem> menuItems;
ListView myListView;
JSONArray jsonArray;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
Bundle extras = getIntent().getExtras();
if(extras!=null){
try{
jsonArray = new JSONArray(extras.getString("Data"));
}catch (Exception e){
e.printStackTrace();
}
}
menuItems = new ArrayList<MyMenuItem>();
for (int i = 0; i < jsonArray.length(); i++) {
try {
MyMenuItem item = new MyMenuItem(jsonArray.getJSONObject(i).getString("title"), jsonArray.getJSONObject(i).getString("imageURL"));
menuItems.add(item);
}catch (Exception e){
e.printStackTrace();
}
}
myListView = (ListView) findViewById(R.id.list);
MenuAdapter adapter = new MenuAdapter(this, menuItems);
myListView.setAdapter(adapter);
myListView.setOnItemClickListener(this);
}
}
MenuItem.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/ivMenuItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="center"
android:src="#drawable/em" />
<TextView
android:id="#+id/tvMenuHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#55000000"
android:paddingBottom="15dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="15dp"
android:textColor="#android:color/white"
android:layout_gravity="left|top"
android:layout_alignBottom="#+id/ivMenuItem"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
</RelativeLayout>
1. Is there an example of how to do this properly?
Your code looks pretty close to perfect. The Adapter's getView method is usually the critical path to optimize. Compare for example Picasso's own example SampleListDetailAdapter.java. The important points it (as well as your code) does
check for & re-use already inflated views, inflation is expensive.
use ViewHolder so you don't have to call findViewById every time. Not terribly expensive on simple views. Also cached afaik.
Picasso.with(context).load(url)... each time you need to display an image. This should finish instantly but still use caches and other magic.
There are some minor optimizations you can add, but I doubt that there are noticeable or even measurable changes:
pure style change: use BaseAdapter#getItem(position). This method
exists for you only. The framework doesn't use it.
#Override
public MyMenuItem getItem(int position) { // << subclasses can use subtypes in overridden methods!
return menuItems.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
...
MyMenuItem row_pos = getItem(position);
Use a sane id method
#Override
public long getItemId(int position) {
return menuItems.indexOf(getItem(position));
}
is equivalent to
#Override
public long getItemId(int position) {
return position;
}
but now infinitely faster. indexOf(Object) scales really badly with the number of objects.
Cache objects that don't change:
MenuAdapter(Context context, List<MyMenuItem> menuItems) {
this.mLayoutInflater = LayoutInflater.from(content);
this.mPicasso = Picasso.with(context);
}
..
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.menu_item, null);
...
mPicasso
.load(row_pos.getItem_image_url())
.into(holder.ivMenu);
2. Is there a way to prevent listview items being destroyed when they are out of screen?
No(*).
..(*) well you can essentially cache the result of getView e.g. in LruCache(position, View) or LruCache(MyMenuItem, View), then don't touch the convertView - they need to remain unconverted or you would kill those views in your cache. Also
#Override
public int getItemViewType(int position) {
return Adapter.IGNORE_ITEM_VIEW_TYPE;
}
seemed to be required because the standard adapter using code assumes that views it removes from visibility are gone. They are not and messing with them messes with your cache and caused weird display problems for me.
3. If so, will it cause problems to keep too many items?
Yes. This behavior is not intendend / expected. There is also more or less nothing you gain. You might be able to save you the call to holder.tvMenuHeader.setText(). Likewise the one to Picasso but both of them should complete instantly. Picasso should have your image cached already. By caching all Views you essentially add another cache that also contains all the images. I would rather check that the picasso cache works as intended and holds most items. The only reason you may want to do it with view caching is for cases that require complicated setup of the view, so it becomes worth caching the completely constructed view rather than just some content parts.
Profile
Profiling can actually tell you where you can / need / should improve. The first to look at IMO is traceview. You'll see if code blocks the main thread which results in choppy list scrolling. If you're doing complicated views and you see that the draw methods are executed most of the time, profile them as well.
http://www.curious-creature.org/docs/android-performance-case-study-1.html
http://blog.venmo.com/hf2t3h4x98p5e13z82pl8j66ngcmry/performance-tuning-on-android
http://www.vogella.com/tutorials/AndroidTools/article.html
I'm creating a simple chess clock -type timer app. I'm trying to show the players and the time they have left as rows in a ListView. I'm using a custom view that extends RelativeLayout for these rows, so that I can give it methods that highlight the player in turn, for example.
Row layout class:
public class GameTimerView extends RelativeLayout {
private TextView nameView;
private TextView timerView;
public GameTimerView(Context context) {
super(context);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.timer_view, this);
loadViews();
}
...
private void loadViews() {
nameView = (TextView)findViewById(R.id.nameView);
timerView = (TextView)findViewById(R.id.timerView);
}
public void setName(String name) {
nameView.setText(name);
}
public void setTime(long timeInMillis) {
timerView.setText(String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(timeInMillis),
TimeUnit.MILLISECONDS.toSeconds(timeInMillis) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeInMillis))
));
}
public void setActive() {
this.nameView.setTextColor(Color.GREEN);
}
public void setInactive() {
nameView.setTextColor(Color.BLACK);
}
}
Row layout XML (timer_view.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/nameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="#+id/timerView"
android:text="#string/player_default_name" />
<TextView
android:id="#+id/timerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="#string/zero_time" />
</RelativeLayout>
Adapter:
public class playerArrayAdapter extends ArrayAdapter<Player> {
private final Context context;
private final ArrayList<Player> players;
public playerArrayAdapter(Context context, ArrayList<Player> values) {
super(context, R.layout.timer_view, values);
this.context = context;
this.players = values;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
GameTimerView playerView = new GameTimerView(context);
players.get(position).setTimerView(playerView);
return playerView;
}
}
Player class setTimerView function:
public void setTimerView(GameTimerView timer) {
this.timerView = timer;
this.timerView.setName(this.name);
this.timerView.setTime(this.totalCountDown);
this.timerView.setInactive();
}
In the activity's onCreate method:
playerArrayAdapter playersAdapter = new playerArrayAdapter(
getApplicationContext(),
game.getPlayers()
);
ListView playersView = (ListView) findViewById(R.id.playerList);
playersView.setAdapter(playersAdapter);
At first this seems to work, and the desired player names and times are rendered to the list properly. However, if I later programmatically call for example Player.timerView.setActive(), nothing happens.
Having looked at dozens of examples of custom adapters for ListViews none of them seems to be using it this way - the view is always inflated directly in Apdater.getView(). I want the flexibility of an extended view class however, but apparently I'm doing something wrong.
So, what's the correct way to use custom view class for ListView rows?
First, when inflating your GameTimerView, you've got a RelativeLayoutinside another.
Second, to answer the question : it might be good not to re-create a view on each call to ArrayAdapter.getView(), but instead modify playerArrayAdapter by adding a cache like this :
private List<View> views;
public playerArrayAdapter(Context context, ArrayList<Player> values) {
super(context, R.layout.timer_view, values);
views = new ArrayList<View>(values.length);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View playerView;
if (position < views.size()) {
playerView = views.get(position);
if (playerView != null)
return playerView;
} else {
while (views.size() < position)
views.add(null);
}
playerView = new GameTimerView(context);
views.add(position, playerView);
players.get(position).setTimerView(playerView);
return playerView;
}
If this successfully corrects your problem, it means that previously, when setting a player as active the ListView was getting all views again for rendering, recreating them, and doing so, erasing any previous state.
As per your getView() method of Adapter, it will create a new view always that might have causing you issue.
If you really want to implement a CustomViewGroup then please refer this good implementation of the custom view here. Hope this will help you to start.
I am trying to implement Android Annotations to my View. But I cant figure out how to do it correctly. My Problem at the moment is that the fields in the View are always NULL.
I think I have a bit of an understanding problem how to use Android Annotations with Views and Adapters. Can someone give me a hint how I would do this correctly?
In my fragment I use the following adapter:
ItemAdapter
#EBean
public class ItemAdapter extends BaseAdapter {
public ItemAdapter(Context context) {
this.context = context;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
GalleryListView view;
if (convertView == null) {
view = GalleryListView_.build(context);
} else {
view = (GalleryListView) convertView;
}
imageUrls = getDocumentListAll();
DDocuments doc = documentProxy.getElementByDocument(imageUrls.get(position));
view.init(imageUrls.get(position), Uri.fromFile(new File(doc.getPath())));
// before I used annotations I did set my Image using this. But now I dont really know how to use this line
// ImageLoader.getInstance().displayImage(Uri.fromFile(new File(doc.getPath())).toString(), holder.image, options, animateFirstListener);
}
}
GalleryListView
#EViewGroup(R.layout.gallery_list)
public class GalleryListView extends LinearLayout {
#ViewById
ImageView image;
#ViewById
TextView text;
public void init(String imageText, Uri imageUri) {
text.setText(imageText);
image.setImageURI(imageUri);
}
}
The problem is that the Views have not been injected when you're making your call to init. So, a solution should be to not set the view's text directly; rather, you want to set a variable that you know will be read into the Views after they have been injected.
One way to do this is to make use of #AfterViews. A method with the #AfterViews annotation will be called after View injection has taken place.
Off the top of my head it would look like this:
#EViewGroup(R.layout.gallery_list)
public class GalleryListView extends LinearLayout {
String mText;
Uri mUri;
#ViewById
ImageView image;
#ViewById
TextView text;
public void init(String imageText, Uri imageUri) {
mText = imageText;
mUri = imageUri;
}
#AfterViews
void afterViews() {
text.setText(mText);
image.setImageURI(mUri);
}
}
This might not be the best solution, but it should get the job done. I suggest keeping the Android Annotation Cookbook handy; there are other similar annotations that will come in handy.
Where are you getting context from?
Try replacing
view = GalleryListView_.build(context);
with
view = new GalleryListView_(context);
In any case, for such a small view I don't see the profit of using Android Annotations, maybe if you had multiple resources I would understand, but for such little code, I recommend you to implement te constructor and inflate the resources yourself there.