Background
Google plus (google+) app has a nice viewing of images on the "highlights" category.
For each section on this screen, they made a header that contains a clickable text and a button to select all photos of this section. for each section they also show the photos in a grid-like manner.
Here's how it looks like :
Here's another more updated image: link .
For some reason, the images here show a sharing button instead of selections, but that's not the issue I wish to talk about.
The problem
I need to have a similar viewing of photos (including button/s on the headers) , but also make the top header always be visible (AKA "pinned header" , like on this project) .
In fact, I don't even care if it will be pinned (though it could be a nice feature).
What I've tried
I've found only 2 libraries that have pinned header gridViews:
StickyGridHeaders - it seemed fine. the API and code design is very nice . However, i've played with it on some devices and found out it crashes with a very weird exception. i've reported about it here, but as I look at the other issues, I think this project won't get fixed anytime soon.
AStickyHeader - this one doesn't have any crashes and bugs that I can find, but it lacks good code design, and it's not so customizable. the header cannot be clicked and it cannot have a button like on Google-plus. i've tried adding it but for some reason the button isn't shown. I've reported about my remarks on it here.
The question
Is there anyone who have tried to handle such a thing?
Any library available or a modification to the libraries I've tried that allow to have what I've written?
since i can't find any other solution, i've decided to make my own solution (code based on another code i've made, here)
it's used on a ListView instead, but it works quite well. you just set the adapter on the listView and you are good to go. you can set exactly how the headers look like and how each cell look like.
it works by having 2 types of rows: header-rows and cells-rows .
it's not the best solution, since it creates extra views instead of having the ListView/GridView (or whatever you use) put the cells correctly, but it works fine and it doesn't crash
it also doesn't have items clicking (since it's for listView), but it shouldn't be hard to add for whoever uses this code.
sadly it also doesn't have the header as a pinned header, but maybe it's possible to be used with this library (PinnedHeaderListView) .
here's the code :
public abstract class HeaderGridedListViewAdapter<SectionData, ItemType> extends BaseAdapter {
private static final int TYPE_HEADER_ROW = 0;
private static final int TYPE_CELLS_ROW = 1;
private final int mNumColumns;
private final List<Row<SectionData, ItemType>> mRows = new ArrayList<Row<SectionData, ItemType>>();
private final int mCellsRowHeight;
private final Context mContext;
public HeaderGridedListViewAdapter(final Context context, final List<Section<SectionData, ItemType>> sections,
final int numColumns, final int cellsRowHeight) {
this.mContext = context;
this.mNumColumns = numColumns;
this.mCellsRowHeight = cellsRowHeight;
for (final Section<SectionData, ItemType> section : sections) {
// add header
Row<SectionData, ItemType> row = new Row<SectionData, ItemType>();
row.section = section;
row.type = TYPE_HEADER_ROW;
mRows.add(row);
int startIndex = 0;
// add section rows
for (int cellsLeft = section.getItemsCount(); cellsLeft > 0;) {
row = new Row<SectionData, ItemType>();
row.section = section;
row.startIndex = startIndex;
row.type = TYPE_CELLS_ROW;
cellsLeft -= Math.min(mNumColumns, cellsLeft);
startIndex += mNumColumns;
mRows.add(row);
}
}
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(final int position) {
return getItem(position).type;
}
#Override
public int getCount() {
return mRows.size();
}
#Override
public Row<SectionData, ItemType> getItem(final int position) {
return mRows.get(position);
}
#Override
public long getItemId(final int position) {
return position;
}
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final Row<SectionData, ItemType> item = getItem(position);
switch (item.type) {
case TYPE_CELLS_ROW:
LinearLayout rowLayout = (LinearLayout) convertView;
if (rowLayout == null) {
rowLayout = new LinearLayout(mContext);
rowLayout.setOrientation(LinearLayout.HORIZONTAL);
rowLayout.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, mCellsRowHeight));
rowLayout.setWeightSum(mNumColumns);
}
final int childCount = rowLayout.getChildCount();
// reuse previous views of the row if possible
for (int i = 0; i < mNumColumns; ++i) {
// reuse old views if possible
final View cellConvertView = i < childCount ? rowLayout.getChildAt(i) : null;
// fill cell with data
final View cellView = getCellView(item.section, item.startIndex + i, cellConvertView, rowLayout);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) cellView.getLayoutParams();
if (layoutParams == null) {
layoutParams = new LinearLayout.LayoutParams(0, mCellsRowHeight, 1);
cellView.setLayoutParams(layoutParams);
} else {
final boolean needSetting = layoutParams.weight != 1 || layoutParams.width != 0
|| layoutParams.height != mCellsRowHeight;
if (needSetting) {
layoutParams.width = 0;
layoutParams.height = mCellsRowHeight;
layoutParams.weight = 1;
cellView.setLayoutParams(layoutParams);
}
}
if (cellConvertView == null)
rowLayout.addView(cellView);
}
return rowLayout;
case TYPE_HEADER_ROW:
return getHeaderView(item.section, convertView, parent);
}
throw new UnsupportedOperationException("cannot create this type of row view");
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(final int position) {
return false;
}
/** should handle getting a single header view */
public abstract View getHeaderView(Section<SectionData, ItemType> section, View convertView, ViewGroup parent);
/**
* should handle getting a single cell view. <br/>
* NOTE:read the parameters description carefully !
*
* #param section
* the section that this cell belongs to
* #param positionWithinSection
* the position within the section that we need to fill the data with. note that if it's larger than what
* the section can give you, it means we need an empty cell (same the the others, but shouldn't show
* anything, can be invisible if you wish)
* #param convertView
* a recycled row cell. you must use it when it's not null, and fill it with data
* #param parent
* the parent of the view. you should use it for inflating the view (but don't attach the view to the
* parent)
*/
public abstract View getCellView(Section<SectionData, ItemType> section, int positionWithinSection,
View convertView, ViewGroup parent);
// ////////////////////////////////////
// Section//
// /////////
public static class Section<SectionData, ItemType> {
private final List<ItemType> mItems;
private final SectionData mSectionData;
public Section(final SectionData sectionData, final List<ItemType> items) {
this.mSectionData = sectionData;
this.mItems = items;
}
public SectionData getSectionData() {
return mSectionData;
}
public int getItemsCount() {
return mItems.size();
}
public ItemType getItem(final int posInSection) {
return mItems.get(posInSection);
}
#Override
public String toString() {
return mSectionData;
}
}
// ////////////////////////////////////
// Row//
// /////
private static class Row<SectionData, ItemType> {
int type, startIndex;
Section<SectionData, ItemType> section;
}
}
Related
I am looking for an infinite loop to display pictures in a ViewPager2.
With a 'slide' page transformer (like default I think) between pictures, and also between last and first item, and first and last item.
The difficulty I found is:
to not have Integer.maxValue items,
having a real transition between first and last element and not a setCurrentItem(x, false) which is not great to see.
I found a solution: having two fake items in the list (first and last element), and when we scroll from second to last we change to the n-2 item (-> last of real item). But the problem is a bad transition to see.
So I imagine a solution more reliable to my situation: duplicate the two lasts items and put them at the beginning of the array.
List before: [A, B, C, D, E, F]
List after transformation: [E', F', A, B, C, D, E, F]
The idea is first to display item A (position 2), when we scroll at item F (position n-1) we force to display item F' (position 1). When we scroll at item E' (position 0) we force to display item E (position n-2).
So, whenever the position, we always have an item on the left and an item on the right, and we always can scroll with a beautiful page transformer.
The limit is to have at least two items. But of course we do not have to scroll with only one item.
Java code:
public class MyPagerActivity extends Activity {
private ViewPager2 pager;
private final MyPagerAdapter adapter = new MyPagerAdapter();
private ViewPager2.OnPageChangeCallback pageChangeCallback = new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageSelected(final int position) {
super.onPageSelected(position);
currentPosition = position;
onFocusChange();
}
#Override
public void onPageScrollStateChanged(final int state) {
super.onPageScrollStateChanged(state);
if (state == ViewPager2.SCROLL_STATE_IDLE) {
if (currentPosition == 0) {
pager.setCurrentItem(fakeSize - 2, false);
} else if (currentPosition == fakeSize - 1) {
pager.setCurrentItem(1, false);
}
} else if (state == ViewPager2.SCROLL_STATE_DRAGGING && currentPosition == fakeSize) {
//we scroll too fast and miss the state SCROLL_STATE_IDLE for the previous item
pager.setCurrentItem(2, false);
}
}
};
private ArrayList<String> itemList;
private int fakeSize;
private int realSize;
private int currentPosition;
#Override
protected void onCreate(#Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.pager = findViewById(R.id.mypager_viewpager);
this.pager.setAdapter(this.adapter);
this.pager.registerOnPageChangeCallback(this.pageChangeCallback);
this.pager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
final ArrayList<String> itemList = null;//your data
this.realSize = itemList.size();
this.fakeSize = this.realSize + 2;
this.itemList = transformListAndAddTwo(itemList);
this.adapter.*updateList*(this.itemList);
final int item = 2; //number two because we have two elements before the chosen one
this.pager.setCurrentItem(item, false);
}
private void onFocusChange() {
//Do something
}
/**
* add two lasts item at beginning on the new array
* #param itemList
* #return
*/
public static ArrayList<String> transformListAndAddTwo(final ArrayList<String> itemList) {
final int size = itemList.size();
final ArrayList<String> listTemp = new ArrayList<>(size + 2);
for (int iPL = 0; iPL <= size + 2; iPL++) {
listTemp.add(itemList.get((iPL + size - 2) % size));
}
return listTemp;
}
}
I have a RecyclerView Adapter which adds data to RecyclerView, it works fine until I scroll up and down and see duplicate TextViews.
So my RecyclerView Loads like this
The issue is where you see tag0 and tag1 - If I scroll down and go back up, a duplicate set of tag0 and tag1 are added - as shown in the following screenshot
It keeps happening over and over again everytime I scroll
My RecyclerView Adapter Code is the following (short version)
class VideoAdapter : RecyclerView.Adapter
{
Context _context;
private List<VideoDisplayModel> _AllLinks;
public VideoAdapter(List<VideoDisplayModel> AllLinks)
{
_AllLinks = AllLinks;
}
public override int ItemCount
{
get
{
return _AllLinks.Count;
}
}
public override long GetItemId(int position)
{
return position;
}
public override int GetItemViewType(int position)
{
return position;
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View listitem = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.VideoRecyclerRow, parent, false);
TextView title = listitem.FindViewById<TextView>(Resource.Id.titleVideo);
ImageView image = listitem.FindViewById<ImageView>(Resource.Id.imageVideo);
LinearLayout tagsLayout = listitem.FindViewById<LinearLayout>(Resource.Id.tagsVideoLayout);
VideoRecylerViewHolder view = new VideoRecylerViewHolder(listitem)
{
Title = title,
Thumb = image,
TagsLayout = tagsLayout
};
_context = parent.Context;
return view;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
var viewHolder = holder as VideoRecylerViewHolder;
var currentLink = _AllLinks[position];
viewHolder.Title.Text = currentLink.Title;
for(int i = 0; i < 2; i++)
{
TextView Txt = new TextView(_context);
Txt.Text = "test"+i.ToString();
Txt.SetBackgroundResource(Resource.Drawable.roundtext);
Txt.SetPadding(30, 10, 30, 10);
Txt.SetTextColor(global::Android.Graphics.Color.ParseColor("#373944"));
Txt.SetTextSize(Android.Util.ComplexUnitType.Dip, 12);
viewHolder.TagsLayout.AddView(Txt);
}
}
I don't understand, what am I doing wrong - Any idea?
Cheers
The RecyclerView recycles the same layout, so if you added a view before, it will add another one.. Make sure you clear the parent before adding the view:
viewHolder.TagsLayout.removeAllViews(); //THIS
for(int i = 0; i < 2; i++) {
...
viewHolder.TagsLayout.AddView(Txt);
}
I don't understand, what am I doing wrong - Any idea?
Yes, OnBindViewHolder gets called multiple times , its the basic fundamental of recyclerView , (it recycles the views).
If you create views dynamically create and add views its gonna be called created multiple times once you scroll .
for(int i = 0; i < 2; i++){
TextView Txt = new TextView(_context);
Txt.Text = "test"+i.ToString();
Txt.SetBackgroundResource(Resource.Drawable.roundtext);
Txt.SetPadding(30, 10, 30, 10);
Txt.SetTextColor(global::Android.Graphics.Color.ParseColor("#373944"));
Txt.SetTextSize(Android.Util.ComplexUnitType.Dip, 12);
viewHolder.TagsLayout.AddView(Txt);
}
This loop is getting executed multiple times !!
So, before the for loop use :-
holder.listitem.removeAllViews();
I have list of contacts which has to be displayed in alphabetic under each alphabet as shown in the image shown
How can I do this in RecyclerView, please suggest a solution.thanks
Sort list with data by name
Iterate via list with data, and in place when current's item first letter != first letter of next item, insert special kind of object.
Inside your Adapter place special view when item is "special".
This is what I did following #divers's post:
as he mentioned I pass a team list to the the adapter which is sorted and alphabets are added before the next name.
this is he code used to set adapter
void updateUI(ArrayList<TeamMember> teamMembers) {
adapter = new TeamMemberActivityAdapter(this, addAlphabets(sortList(teamMembers)));
recList.setAdapter(adapter);
recList.setVisibility(View.VISIBLE);
spinningProgressView.setVisibility(View.GONE);
}
code to sort the team list obtained from server is given below:
ArrayList<TeamMember> sortList(ArrayList<TeamMember> list) {
Collections.sort(list, new Comparator<TeamMember>() {
#Override
public int compare(TeamMember teamMember1, TeamMember teamMember2) {
return teamMember1.getFullname().compareTo(teamMember2.getFullname());
}
});
return list;
}
while adding alphabets to the list I am setting a type value to know whether its alphabet or team name to check this inside the adapter for showing corresponding layout .the code for that is as shown below:
ArrayList<TeamMember> addAlphabets(ArrayList<TeamMember> list) {
int i = 0;
ArrayList<TeamMember> customList = new ArrayList<TeamMember>(); TeamMember firstMember = new TeamMember();
firstMember.setFullname(String.valueOf(list.get(0).getFullname().charAt(0)));
firstMember.setType(1);
customList.add(firstMember);
for (i = 0; i < list.size() - 1; i++) {
TeamMember teamMember = new TeamMember();
char name1 = list.get(i).getFullname().charAt(0);
char name2 = list.get(i + 1).getFullname().charAt(0);
if (name1 == name2) {
list.get(i).setType(2);
customList.add(list.get(i));
} else {
list.get(i).setType(2);
customList.add(list.get(i));
teamMember.setFullname(String.valueOf(name2));
teamMember.setType(1);
customList.add(teamMember);
}
}
list.get(i).setType(2);
customList.add(list.get(i));
return customList;
}
And finally inside your adapter check if the item is teamMember name or alphabet and display corresponding layout as shown below:
#Override
public int getItemViewType(int position) {
int viewType = 0;
if (mMembers.get(position).getType() == TYPE_LETTER) {
viewType = TYPE_LETTER;
} else if (mMembers.get(position).getType() == TYPE_MEMBER) {
viewType = TYPE_MEMBER;
}
return viewType;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater mInflater = LayoutInflater.from(viewGroup.getContext());
switch (viewType) {
case TYPE_LETTER:
ViewGroup vGroupImage = (ViewGroup) mInflater.inflate(R.layout.board_team_letter_item, viewGroup, false);
ViewHolderLetter image = new ViewHolderLetter(vGroupImage);
return image;
case TYPE_MEMBER:
ViewGroup vGroupText = (ViewGroup) mInflater.inflate(R.layout.board_team_member_item, viewGroup, false);
ViewHolderMember text = new ViewHolderMember(vGroupText);
return text;
default:
ViewGroup vGroupText2 = (ViewGroup) mInflater.inflate(R.layout.board_team_member_item, viewGroup, false);
ViewHolderMember text1 = new ViewHolderMember(vGroupText2);
return text1;
}
}
hope this could help you. all the best
compare your model and get first character from title ....
private void getHeaderListLatter(ArrayList<CountriesModel> usersList) {
Collections.sort(usersList, new Comparator<CountriesModel>() {
#Override
public int compare(CountriesModel user1, CountriesModel user2) {
return String.valueOf(user1.name.charAt(0)).toUpperCase().compareTo(String.valueOf(user2.name.charAt(0)).toUpperCase());
}
});
String lastHeader = "";
int size = usersList.size();
for (int i = 0; i < size; i++) {
CountriesModel user = usersList.get(i);
String header = String.valueOf(user.name.charAt(0)).toUpperCase();
if (!TextUtils.equals(lastHeader, header)) {
lastHeader = header;
mSectionList.add(new CountriesModel(header,true));
}
mSectionList.add(user);
}
}
and in your adapter getItemViewType Layout like this ....
#Override
public int getItemViewType(int position) {
if (mCountriesModelList.get(position).isSection) {
return SECTION_VIEW;
} else {
return CONTENT_VIEW;
}
}
for complete reference .
https://github.com/sayanmanna/LetterSectionedRecyclerView
I'm currently using this. It's very easy to implement, compatible with RecyclerView adapter, and so lightweight you'd barely call it a library!
You can achieve it with this library.
There is a full example here of how to add headers.
And if you want to implement the search functionality, there is also a full example here, this is the result:
https://github.com/emilsjolander/StickyListHeaders
I hope this is what You want.
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 an Activity that hosts multiple fragments using the actionbar's tab functionality. One of those fragments contains a ListView. Upon this tab being selected, I'd like to select a certain item.
To do this programmatically, I use the following code (where calls is the ListView)
private void selectItem(int position)
{
long itemId = calls.GetItemIdAtPosition(position);
calls.PerformItemClick(calls, position, itemId);
}
If this ListView has been rendered, and I'm calling this, no problem. However, if I call it from onResume, then the code executes but nothing is selected in the end. I figure this is because at the point where I'm calling selectItem, not all items of the ListView have been rendered yet. If however I start off a background thread, sleep for a couple hundred milliseconds, then run the same code (in the ui thread of course), everything is fine, but this is an ugly hack.
Now you might be wondering, "why isn't he using calls.setSelection"? The thing is, I'm using a custom layout that performs expansion - so I need to actually click on the item I want selected (which in turn triggers the layout expansion for the item selected). However, I can call the code that is performed on PerformItemClick directly, the results will be the same (the layout expansion isn't performed).
Isn't there any way for me to catch the "Listview has finished rendering all viewable items" point in time, and then execute my selectItem call at that point? In ASP.NET, I have an event on every UI item telling me when it is done rendering, so I do item selection at that point but I haven't found anything.
Regards
Stephan
Here's the Adapter I'm using
public class ActiveCallsAdapter: ObservableAdapter<Call>
{
public ActiveCallsAdapter(Activity activity, ObservableCollection<Call> calls)
: base(activity, calls)
{
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
var view = (convertView ?? context.LayoutInflater.Inflate(Resource.Layout.Call, parent, false)) as LinearLayout;
//View view = convertView;
//if (view == null) // no view to re-use, create new
// view = context.LayoutInflater.Inflate(Resource.Layout.Call, null);
SetTextView(view, Resource.Id.CallerName, item.CallerName);
SetTextView(view, Resource.Id.CallerNumber, item.CallerNumber);
SetTextView(view, Resource.Id.CallStatus, item.State.ToString());
SetTextView(view, Resource.Id.CallDuration, item.Duration);
return view;
}
public void Update(LinearLayout view, Call item)
{
SetTextView(view, Resource.Id.CallerName, item.CallerName);
SetTextView(view, Resource.Id.CallerNumber, item.CallerNumber);
string identifier = "callState_" + item.State.ToString();
int resourceId = Application.Context.Resources.GetIdentifier(identifier, "string", Application.Context.PackageName);
string callStateString = item.State.ToString();
if (resourceId != 0)
{
try
{
callStateString = Application.Context.Resources.GetString(resourceId);
}
catch (Exception e)
{
AndroidLogModel.Model.AddLogMessage("ActiveCallsAdapter", "Unable to find call state string with resource id " + resourceId + " state string: " + identifier, 3);
}
}
SetTextView(view, Resource.Id.CallStatus, callStateString);
//SetTextView(view, Resource.Id.CallDuration, item.Duration);
}
public void UpdateDuration(LinearLayout view, Call item)
{
SetTextView(view, Resource.Id.CallDuration, item.Duration);
}
}
And the base class of that adapter
public class ObservableAdapter<T>: BaseAdapter<T>
{
protected readonly Activity context;
protected readonly ObservableCollection<T> items;
public ObservableAdapter(Activity context, ObservableCollection<T> collection)
{
this.context = context;
this.items = collection;
//this.collection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(collection_CollectionChanged);
this.items.CollectionChanged += (sender, e) => NotifyDataSetChanged();
}
void collection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyDataSetChanged();
}
public override T this[int position]
{
get { return items[position]; }
}
public override int Count
{
get { return items.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
var view = (convertView ?? context.LayoutInflater.Inflate(Resource.Layout.Call, parent, false)) as LinearLayout;
// configure view here
return view;
}
protected void SetTextView(LinearLayout view, int id, string text)
{
var textView = view.FindViewById<TextView>(id);
if (textView != null)
textView.SetText(text, TextView.BufferType.Normal);
}
}
My Mono skills are limited so I don't know if I fully understood your adapter, anyway I've adapted some old code and made an adapter that expands a single item when click, also it will move the ListView in onResume to a desired position:
private static class CustomAdapter extends BaseAdapter {
// the data
private ArrayList<String> mData;
// an int pointing to a position that has an expanded layout,
// for simplicity I assume that you expand only one item(otherwise use
// an array or list)
private int mExpandedPosition = -1; // -1 meaning no expanded item
private LayoutInflater mInflater;
public CustomAdapter(Context context, ArrayList<String> items) {
mInflater = LayoutInflater.from(context);
mData = items;
}
public void setExpandedPosition(int position) {
// if the position equals mExpandedPosition then we have a click on
// the same row so simply toggle the row to be gone again
if (position == mExpandedPosition) {
mExpandedPosition = -1;
} else {
// else change position of the row that was expanded
mExpandedPosition = position;
}
// notify the adapter
notifyDataSetChanged();
}
#Override
public int getCount() {
return mData.size();
}
#Override
public String getItem(int position) {
return mData.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.ad_expandedelement,
parent, false);
}
((TextView) convertView.findViewById(R.id.textView1))
.setText(getItem(position));
// see if there is an expanded position and if we are at that
// position
if (mExpandedPosition != -1 && mExpandedPosition == position) {
// if yes simply expand the layout
convertView.findViewById(R.id.button1).setVisibility(
View.VISIBLE);
} else {
// this is required, we must revert any possible changes
// otherwise the recycling mechanism will hurt us
convertView.findViewById(R.id.button1).setVisibility(View.GONE);
}
return convertView;
}
}
The onListItemClick will simply be:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// set the expanded(or collapsed if it's a click on the same row that
// was previously expanded) row in the adapter
((CustomAdapter) getListView().getAdapter())
.setExpandedPosition(position);
}
and in onResume will have:
#Override
protected void onResume() {
super.onResume();
// set the position to the desired element
((CustomAdapter) getListView().getAdapter()).setExpandedPosition(15);
// set the selection to that element so we can actually see it
// this isn't required but has the advantage that it will move the
// ListView to the desired
// position if not visible
getListView().setSelection(15);
}
The R.layout.ad_expandedelement is a simple vertical LinearLayout with a TextView and an initially hidden(visibility set to gone) Button. For this Button I change the visibility to simulate expanding/collapsing a row in the ListView. You should be able to understand my code, if you want I can post on github the full sample.
While I'm not sure of the exact equivalent in C#/Mono, the Android framework provides a callback on Activity called onWindowFocusChanged() that indicates the period when the Window associated with a given Activity is visible to the user. You may have better luck waiting to call your selection method until that time, as the ListView should be measured and laid out by that point. In Java, it would be something like this:
#Override
public void onWindowFocusChanged (boolean hasFocus) {
if (hasFocus) {
selectItem(position);
}
}
You may need to have a bit more logic in there, this callback is directly associated with window focus and isn't a true lifecycle method. I can get called multiple times if you are displaying Dialogs or doing other similar operations.