Hi Guys My question is very simple
I want to add images in a row like a flowlayout or GridLayout as you can see in the Image below
Above that layout i want to add a button such that it comes in between rows.
When i scroll my grid View , the button Image also scrolls respective with the gridview.
Can any one suggest me some ideas how it can be possible
If it's always a fourth item - than must be no problem.
Impelment a GridView with android:numColumns="3"
In your Adapter implement three view types
The idea is to add two blank items in a second row and a button to the middle.
private static final int TYPE_NORMAL = 0;
private static final int TYPE_BLANK = 1;
private static final int TYPE_BUTTON = 2;
#Override
public int getViewTypeCount() {
return 3;
}
#Override
public int getCount() {
return yourdata.size() + 3;
}
// return your real data by skipping row with the button
#Override
public Object getItem(int position) {
if (position > 3) {
position += 3;
}
return yourdata.get(position);
}
// return your real data ID by skipping row with the button The button probably should catch it's own onClickListemer
#Override
public long getItemId(int position) {
if (position > 3) {
position += 3;
}
return yourdata.get(position).getId();
}
#Override
public int getItemViewType(int position) {
switch(position) {
case 4:
case 6:
return TYPE_BLANK;
case 5:
return TYPE_BUTTON;
default:
return TYPE_NORMAL;
}
}
// only your items should be clickable
#Override
public boolean isEnabled(int position) {
return position < 4 && position > 6;
}
// nope, only your specific data items are enabled.
#Override
public boolean areAllItemsEnabled() {
return false;
}
In yout getView method just check the item view type and inflate the proper view.
For more details implementing adapters with multiple item types refer to example of ListView with section headers etc.
How to generate a ListView with headers above some sections?
http://w2davids.wordpress.com/android-sectioned-headers-in-listviews/
Related
I am implementing recyclerview with multiple layouts.Usually we have multiple viewholders for different layouts and override other methods as per the required layout.I have successfully implemented this.But now i have a different scenario like: A recyclerview that shows some videos (say 3) then another layout(say layout x), again 3 videos and then again layout x and so on.Suppose i have 10 videos then in this case the itemcount would be 10 + 3 as 3 layout x would be displayed.But the videos are loaded while scrolling.So how can i determine the number of views to return in getItemCount();
I mean
#Override
public int getItemCount() {
return ListofVideos.size() + "WHAT??"
}
layout is like this
If all the videos are loaded at at once then it is easy to calculate the number of views like if i have 21 videos i would have total 27 views(i.e 21 videos and 6 layout X views). But when the list is loaded on scroll how can i determine the number of views?
Your Adapter is responsible to populate view so it has all views of your RecyclerView while your ListofVideos (may) have only video links.
Whenever you scroll your RecyclerView, Adapter is responsible to inflate views.
What you should do?
Create an interface
public interface BaseItem {
int ITEM_TYPE_HEADER = 0;
int ITEM_TYPE_SUB_HEADER = 1;
int ITEM_TYPE_ROW_NORMAL = 2;
int getItemType();
}
And implement this interface with your adapter's video item like
public class YourAdapterVideoItem implements BaseItem {
// rest of your code
#Override
public int getItemType() {
return ITEM_TYPE_ROW_NORMAL;
}
}
Create your adapter's header item
public class YourAdapterHeaderItem implements BaseItem {
// rest of your code
#Override
public int getItemType() {
return ITEM_TYPE_HEADER;
}
}
Update your adapter with
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<BaseItem> items = new ArrayList<BaseItem>();
#Override
public BaseRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
BaseRecyclerViewHolder holder;
switch (viewType) {
case BaseItem.ITEM_TYPE_ROW_NORMAL:
default:
// inflate your default items
break;
case BaseItem.ITEM_TYPE_HEADER:
// inflate your default items
break;
}
return holder;
}
#Override
public void onBindViewHolder(BaseRecyclerViewHolder viewHolder, int position) {
BaseItem base = getItemAt(position);
switch (base.getItemType()) {
case BaseItem.ITEM_TYPE_HEADER:
// populate your header view
break;
case BaseItem.ITEM_TYPE_ROW_NORMAL:
// populate your actual view
break;
}
}
#Override
public int getItemCount() {
return items == null ? 0 : items.size();
}
#Override
public int getItemViewType(int position) {
return getItemAt(position).getItemType();
}
public BaseItem getItemAt(int position) {
return items == null ? null : items.get(position);
}
}
When you want to add header use YourAdapterHeaderItem for your videos use YourAdapterVideoItem.
Hope this helps
Edit
For adding headers in GridLayoutManager have a look at RecyclerView GridLayoutManager with full width header
I have a RecyclerView that can show items as list, small grids or large grid and this can be change at runtime. Depending on what style user chooses i inflate different layout in onCreateViewHolder.
I also use layoutManger.setSpanSizeLookUp() to switch between styles. My code looks like this
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
return 1;
else
return columnCount; //show one item per row
}
});
#Override
public void onClick(View v) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
showType = ProductAdapter.SHOW_TYPE_LARGE_GRID;
else
showType = ProductAdapter.SHOW_TYPE_SMALL_GRID;
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
adapter = new ProductAdapter(getActivity(), productList, showType);
recyclerView.setAdapter(adapter);
layoutManager.scrollToPosition(firstVisibleItem);
}
The problem is to force onCreateViewHolder to be called I'm creating a new object every time user changes the style. Is there any other way?! to force onBindViewHolder() to be recalled. I simply use adapter.notifyDataSetChanged() How can i get something similar for onCreateViewHolder?
Any solution that doesn't uses multiple adapters is good enough!
What you need to do is:
Modify your Adapter:
Specify two types of Views that your Adapter can inflate:
private static final int LARGE_GRID_ITEM = -1;
private static final int SMALL_GRID_ITEM = -2;
Create a field that can store current type mCurrentType
Use your Adapter's getItemViewType. For example like this:
#Override
public int getItemViewType (int position) {
return mCurrentType;
}
In your createViewHolder use the viewType to decide what type of ViewHolder you need to create.
public final RecyclerView.ViewHolder createViewHolder (ViewGroup parent, int viewType){
if (viewType == LARGE_GRID_ITEM) {
//return large grid view holder
} else {
//return small grid view holder
}
}
Additionally you can create methods:
public void toggleItemViewType () {
if (mCurrentType == LARGE_GRID_ITEM){
mCurrentType = SMALL_GRID_ITEM;
} else {
mCurrentType = LARGE_GRID_ITEM;
}
}
public boolean displaysLargeGrid(){
return mCurrentType == LARGE_GRID_ITEM;
}
Modify the code you posted:
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (adapter.displaysLargeGrid()) {
return 1;
} else {
return columnCount; //show one item per row
}
}
});
#Override
public void onClick(View v) {
adapter.toggleItemViewType();
adapter.notifyDataSetChanged();
}
Its not the optimal choice but it's better to create a new Adapter, which will call onCreateViewHolder(). This way you can avoid your troubles, by the cost of very tiny performance issues.
I'm attempting to create a listview which has banner ads at every nth interval. In my code, it's every 6 items. I've based my code on the example here. The example provided is based on having a banner ad at the first and last of the listview, so I've encountered problems when trying to intersperse the banner ads within the listview at equal intervals. Specifically, my ads don't display every 6th time. The result I have from the code below is displaying two consecutive ads at positions 7,8 and then my third at position 13. I'm not sure what could be causing the ad at position 8 to appear.
Any idea how I can fix this?
Code below...
Firstly, the creation of my SimpleCursorAdapter, which contains about 14 strings to display in the listview in this case.
// Create and populate listview
final ListView listview = (ListView) findViewById(android.R.id.list);
// SimpleCursorAdapter method
Cursor c = dbh.getAllCategoriesCursor();
startManagingCursor(c);
String[] from = new String[] { "name" };
int[] to = new int[] { R.id.category_label};
// Now create an array adapter and set it to display using our row
SimpleCursorAdapter cursorAdapter =
new SimpleCursorAdapter(this, R.layout.rowlayout, c, from, to, 0);
setListAdapter(new ListViewAdapter(this, cursorAdapter));
And here is my ListViewAdapter, extending the BaseAdapter class
public class ListViewAdapter extends BaseAdapter {
private Activity mContext;
private SimpleCursorAdapter listAdapter;
private LayoutInflater mLayoutInflater;
private int k = 6; // interval ad every 6 items
int baseItems;
int noAds; // used for listview offset
// Constructor takes in a BaseAdapter
public ListViewAdapter(Activity activity, SimpleCursorAdapter delegate) {
mContext = activity;
listAdapter = delegate;
baseItems = listAdapter.getCount();
noAds = baseItems / k;
mLayoutInflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
// Total count includes list items and ads.
return baseItems + noAds;
}
#Override
public Object getItem(int position) {
// Return null if an item is an ad. Otherwise return the delegate item.
if (isItemAnAd(position)) {
return null;
}
return listAdapter.getItem(getOffsetPosition(position));
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getViewTypeCount() {
return listAdapter.getViewTypeCount() + noAds;
}
#Override
public int getItemViewType(int position) {
if (isItemAnAd(position)) {
return listAdapter.getViewTypeCount();
} else {
return listAdapter.getItemViewType(getOffsetPosition(position));
}
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
return (!isItemAnAd(position)) && listAdapter.isEnabled(getOffsetPosition(position));
}
private boolean isItemAnAd(int position) {
// Place an ad at the first and last list view positions.
// -- should override for every kth positions
if (position == 0) return false;
// Calculate offsets caused by ads already embedded
int offset = 0;
if (position > k){
int div = position / k;
offset = div;
}
return ((position-offset) % k == 0);
}
// Get the position that is offset by the insertion of the ads
private int getOffsetPosition(int position) {
return position - noAds;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Display every n list items
if (isItemAnAd(position)) {
if (convertView instanceof AdView) {
// Don’t instantiate new AdView, reuse old one
return convertView;
} else {
// Create a new AdView
AdView adView = new AdView(getApplicationContext());
adView.setAdSize(AdSize.BANNER);
adView.setAdUnitId(BANNER_AD_ID);
// Disable focus for sub-views of the AdView to avoid problems with
// trackpad navigation of the list.
for (int i = 0; i < adView.getChildCount(); i++)
{
adView.getChildAt(i).setFocusable(false);
}
adView.setFocusable(false);
// Convert the default layout parameters so that they play nice with
// ListView.
float density = getApplicationContext().getResources().getDisplayMetrics().density;
int height = Math.round(AdSize.BANNER.getHeight() * density);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
height);
adView.setLayoutParams(params);
AdRequest bannerIntermediateReq = new AdRequest.Builder()
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
.addTestDevice("d9e108ab") //means that a test ad is shown on my phone
.build();
adView.loadAd(bannerIntermediateReq);
return adView;
}
} else {
// Offload displaying other items to the delegate
return listAdapter.getView(position - (int) Math.ceil(position / k),
convertView, parent);
}
}
}
Update 9/6... Found a workaround of sorts that creates an ad at the nth interval, followed by all following ads at n-1 intervals. So if I set n=8 I'll get an ad inserted after 8 list items, after ads inserted every 7 items after that. I'm quite happy to settle for this given the amount of time I've sunk into it. It's probably an off-by-one bug somewhere that I cannot find whatsoever. Hope this helps someone at some point. Here's the modifications:
private boolean isItemAnAd(int position) {
if (position < k) return false;
// Calculate current offset caused by ads already embedded
if (position==k){
return true;
}
else {
return isItemAnAd(position-k);
}
}
and
// Get the position that is offset by the insertion of the ads
private int getOffsetPosition(int position) {
int currentNoAds = position / k;
return position - currentNoAds;
}
and, in getView...
return listAdapter.getView(getOffsetPosition(position) ,
convertView, parent);
i have been trying to swap items in a Grid view, and this is where i got:
xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/parent_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<GridView
android:id="#+id/grid_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:horizontalSpacing="10dip"
android:numColumns="4"
android:verticalSpacing="10dip" />
</RelativeLayout>
main activity class:
public class MainActivity extends Activity implements OnDragListener,
OnItemLongClickListener {
ArrayList drawables;
GridView gridView;
private BaseAdapter adapter;
private int draggedIndex = -1;
private int droppedIndex = -1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawables = new ArrayList();
drawables.add(R.drawable.ic_launcher);
drawables.add(R.drawable.ic_launcher1);
drawables.add(R.drawable.ic_launcher2);
drawables.add(R.drawable.ic_launcher);
drawables.add(R.drawable.ic_launcher);
drawables.add(R.drawable.ic_launcher);
drawables.add(R.drawable.ic_launcher);
drawables.add(R.drawable.ic_launcher);
gridView = (GridView) findViewById(R.id.grid_view);
gridView.setOnItemLongClickListener(MainActivity.this);
gridView.setAdapter(adapter = new BaseAdapter() {
#Override
// Get a View that displays the data at the specified position in
// the data set.
public View getView(int position, View convertView,
ViewGroup gridView) {
// try to reuse the views.
ImageView view = (ImageView) convertView;
// if convert view is null then create a new instance else reuse
// it
if (view == null) {
view = new ImageView(MainActivity.this);
}
view.setImageResource((Integer) drawables.get(position));
view.setTag(String.valueOf(position));
return view;
}
#Override
// Get the row id associated with the specified position in the
// list.
public long getItemId(int position) {
return position;
}
#Override
// Get the data item associated with the specified position in the
// data set.
public Object getItem(int position) {
return drawables.get(position);
}
#Override
// How many items are in the data set represented by this Adapter.
public int getCount() {
return drawables.size();
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onDrag(View view, DragEvent dragEvent) {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
// Ignore this event
return true;
case DragEvent.ACTION_DRAG_ENTERED:
// Ignore this event
return true;
case DragEvent.ACTION_DRAG_EXITED:
// Ignore this event
return true;
case DragEvent.ACTION_DRAG_LOCATION:
// Ignore this event
return true;
case DragEvent.ACTION_DROP:
// Dropped inside a new view\
adapter.notifyDataSetChanged();
ImageView v2 = (ImageView)view.getParent();
final int position1 = gridView.getPositionForView(v2);
if (position1 >= 0)
{
final long droppedIndex = gridView.getAdapter().getItemId(position1);
}
Object item1 = gridView.getAdapter().getItem(draggedIndex);
Object item2 = gridView.getAdapter().getItem(droppedIndex);
drawables.remove(draggedIndex);
drawables.remove(droppedIndex);
drawables.add(droppedIndex,item1);
drawables.add(draggedIndex,item2);
draggedIndex = -1;
droppedIndex = -1;
adapter.notifyDataSetChanged();
case DragEvent.ACTION_DRAG_ENDED:
//
view.setOnDragListener(null);
return true;
}
return false;
}
#Override
public boolean onItemLongClick(AdapterView gridView, View view,
int position, long row) {
ClipData.Item item = new ClipData.Item((String) view.getTag());
ClipData clipData = new ClipData((CharSequence) view.getTag(),
new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item);
view.startDrag(clipData, new View.DragShadowBuilder(view), null, 0);
view.setVisibility(View.INVISIBLE);
draggedIndex = position;
return true;
}
}
my problem is in DragEvent.ACTION_DROP. I works like this:
I drag one item and, when drop it in another place, the item disappears. And that is all.
Supposedly, first retrieve both positions: the position of the item dragged (draggedIndex) and the position where the item is dropped (droppedIndex). After, i remove both items and add them to the array again in the opposite positions (the item dragged goes to droppedIndex and the other goes to the draggedIndex, so they are exchanged/swaped)
I wonder if it is a good way to do this, or if i made any mistakes trying to retrieve the dropped position (droppedIndex).
any ideas?
Before removing the item , just add the item into dropped position. Then remove the corresponding item by incrementing the dropped position by 1.
case DragEvent.ACTION_DROP:
....
drawables.add(droppedIndex,item1);
drawables.add(draggedIndex+1,item2);
drawables.remove(draggedIndex+2);
drawables.remove(droppedIndex+2);
....
hope this will help you.
I took a slightly different approach to get the dropped index. For an ACTION_DROP event, getX() and getY() return the X and Y position of the drag point at the moment of the drop, using the coordinate system of the View that received the drop (i.e. the gridview).
float dropX = event.getX();
float dropY = event.getY();
Once you have the x and y co-ordinates you can calculate the corresponding row and column in your grid view, and then use something like the following to get the index of the object in your data array (I have a 4 x 4 grid):
index = row * 4 + column;
Finally, I'm using an array list (targetDrawables) to hold the references to my gridview contents, so to reorder use:
targetDrawables.remove(draggedIndex);
targetDrawables.add(droppedIndex, draggedContents);
// Invalidate the view to force a redraw of the Grid View
imageAdapter.notifyDataSetChanged();
Your code is fine; just replace
Object item1 = gridView.getAdapter().getItem(draggedIndex);
Object item2 = gridView.getAdapter().getItem(droppedIndex);
drawables.remove(draggedIndex);
drawables.remove(droppedIndex);
drawables.add(droppedIndex,item1);
drawables.add(draggedIndex,item2);
with
Collections.swap(drawables, draggedIndex, droppedIndex);
It swaps position of list and now it works!
I have the following, very simple test program for using a ListView. I create a ListView and set it as the content view. I set a ListAdapter which supplies the rows. There are 30 rows, and each row consists of a LinearLayout ViewGroup. Into that ViewGroup, I place a TextView and a Button. When I run the program, I find that I cannot select rows of the list. I can, however, scroll the list and click the button.
If I remove the button from the LinearLayout (so that it contains only the TextView), then I am able to select rows of the list. I would
like to be able to have buttons on my individual row views, and still be able to select rows of the list. On another forum, someone said that this was possible, but I am at a loss as to how to accomplish it.
Can anyone give me a clue?
Thanks.
public class ListViewTest extends Activity implements ListAdapter
{
int m_count;
DataSetObserver m_observer;
public ListViewTest()
{
m_count = 30;
m_observer = null;
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
ListView lv = new ListView(this);
lv.setAdapter(this);
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
setContentView(lv);
}
#Override
public boolean areAllItemsEnabled() {
return true;
}
#Override
public boolean isEnabled(int position) {
return true;
}
#Override
public int getCount()
{
return m_count;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LinearLayout vg = new LinearLayout(this);
TextView tv = new TextView(this);
tv.setText("ListItem");
Button bv = new Button(this);
bv.setText("Button");
vg.addView(tv);
vg.addView(bv);
return(vg);
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public boolean hasStableIds() {
return false;
}
#Override
public boolean isEmpty() {
return false;
}
#Override
public void registerDataSetObserver(DataSetObserver observer)
{
m_observer = observer;
}
#Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
}
As the other answers point out, whether or not you can select ListView rows as full items on their own depends on whether or not those rows contain focusable items. However, the solution is usually not setting focusable=false on your buttons or the like. That will prevent your app from being navigable with a d-pad, trackball, arrow keys, or what have you.
You want your list items to be able to control their own focus properties. You want setItemsCanFocus. This will disable the special focus/selection handling that ListView normally uses to treat list items as a single unit.
Now you can set a listener on the layout you use as the top-level element in your rows, set a stateful background drawable on it to display focus/press state, as well as focusLeft/Right properties to control focus shifting within the item itself.