I am trying to make a listview appear to fade to black towards the top of it. Essentially I want to turn something like this:
in to something like this:
I tried two different approaches to this:
The first one was to override the onDraw() of the ListView, this did not work, my "extra" drawing happened behind the listView. The second approach was to put another view on top of the listView, this looks right, but if the user tries to scroll the list by touching the screen where the view is it does not scroll, it seems like the view consumes the touchevent, so about a third of the list is untouchable.
Any tips on how I can implement this?
You can assign the listitem background in a customised view. In getview (of adapter class) based on the item position. Create a gradient in xml and increase the alpha value based on the position.
This is the peice of code which is working for me.
listItem.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="match_parent"
android:gravity="center_vertical"
android:minHeight="64dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="17sp"
android:textColor="#android:color/white"
android:id="#+id/textView"/>
</RelativeLayout>
ItemAdapter.java
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(rowResourceId, parent, false);
TextView textView = (TextView) rowView.findViewById(R.id.textView);
int id = Integer.parseInt(Ids[position]);
textView.setText(Model.GetbyId(id).name);
// this is the main idea behind the UX look n feel.
rowView.setBackgroundColor(context.getResources().getColor(android.R.color.black));
textView.setAlpha((float) position/Ids.length);
return rowView;
}
Please feel free to use holder design pattern in the getView. This is just a proof of concept.
Related
I have a custom ListView item that has an ImageButton in it, and when I click it, the click events don't come through until the list item as a whole is interacted with, such as scrolling the list or tapping somewhere else on the list item. So for instance, if I click the ImageButton 5 times, nothing will happen, but then when I scroll the list, all 5 click events will come through simultaneously.
After a lot of research, I've come to find this is a common question, but none of the solutions I've found have worked for me so far. This question was quite helpful in learning some of the quirks of view interaction, and most other solutions I found used a similar approach to the accepted answer on that question, but unfortunately none of them worked for my particular situation.
So what I need to happen is to handle the click events for the ImageButton on the list item, as well as the click event for the list item itself. I've tried setting android:focusable="false" and android:focusableInTouchMode="false" on pretty much every view element involved in the display of this list. I've also tried setting these attributes programmatically. I also tried setting android:descendantFocusability="blocksDescendants" on several view elements including the ListView, the LinearLayout of the row item, and the CardView that contains all of this stuff.
The confusing part about all of this, is that I have this working just fine in another Activity of the same app. I have an ImageButton in a custom row layout of a ListView, and the onClick events work just fine for that. So it has to be something with my setup on this Activity. Here's some code:
The getView() of my custom BaseAdapter:
public View getView(int position, View convertView, ViewGroup parent) {
FavoritesItemViewHolder favoriteItem = favoritesList.get(position);
if(convertView == null) {
convertView = favoriteItem.getNewConvertView(parent);
} else {
if(favoriteItem.getConvertView() == null) {
convertView = favoriteItem.getNewConvertView(parent);
} else {
convertView = favoriteItem.getConvertView();
}
}
return convertView;
}
The view inflation of my individual view holders:
public View getNewConvertView(ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.favorites_list_item, parent, false);
TextView favoriteText = (TextView) convertView.findViewById(R.id.favoritesItemText);
favoriteText.setText(itemTitle);
//Here is where I'm setting the OnClickListeners for each row...
ImageButton clearButton = (ImageButton) convertView.findViewById(R.id.favoritesClearButton);
clearButton.setVisibility(View.VISIBLE);
if(itemTitle.equals(context.getResources().getString(R.string.no_favorites))) {
clearButton.setVisibility(View.INVISIBLE);
} else {
clearButton.setVisibility(View.VISIBLE);
clearButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("CLEAR FAVORITE", "The clear button was just clicked on a favorites item...");
}
});
}
return convertView;
}
And here is the XML for my custom row layouts...
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/favoritesListItemContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/favoritesItemText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_gravity="center_vertical"
android:textSize="16sp"
android:textColor="#android:color/black"
android:layout_weight="0.9"/>
<ImageButton
android:id="#+id/favoritesClearButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:src="#drawable/ic_clear_x"
android:layout_gravity="center_vertical"
android:scaleType="centerInside"
android:adjustViewBounds="true"
android:background="#android:color/white"
style="?android:attr/borderlessButtonStyle"
android:layout_weight="0.1" />
</LinearLayout>
Any help would be appreciated. I'm just completely stumped here.
UPDATES
In this Activity, I'm using getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); in order to disable a SearchView and some other UI, and when the SearchView receives focus, suddenly my ListView items work as intended. So I'm guessing this has an issue with the ListView or the Window it's in having no focus, and so the click events don't come through until something on the screen receives focus.
What's strange is, when the keyboard is showing on the screen, I can click the list items and their ImageButtons and all the click events are handled correctly. But when I dismiss the keyboard, it breaks again.
There are known problems with ListView interfering with the touch events of views inside each item. You should switch to RecyclerView instead. ListView is seen as essentially deprecated for new apps with RecyclerView becoming its replacement.
I have a list view displaying items from a custom adapter which extends ArrayAdapter. Each item is a custom layout with a RelativeLayout being the root view. Now, I want this RelativeLayout to be centered horizontally inside the list view, but I everything I tried seems to fail.
Here's my custom adapter getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = mInflater.inflate(R.layout.screens_listview_row, parent, false);
return row;
}
And here's the file screens_listview_row.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
.....
android:layout_gravity="center_horizontal" >
</RelativeLayout>
At first, it seemed like my layout parameters in the RelativeLayout were completely ignored, and it really was the case because i used inflate(R.layout.screens_listview_row, null), what is a problem like this answer says.
So now the only thing ignored is the layout_gravity parameter. I also tried layout_marginLeft (Everything in this layout is in absolute sizes so I could center it myself by giving a left margin...) but Android ignored it too. What is the problem here?
try to use gravity instead of layout_gravity (as i know gravity is for the content of layout)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
.....
android:gravity="center_horizontal" >
</RelativeLayout>
I didn't find out how to control the position of the items inside the ListView, so instead I made the ListView width exactly as the items' width, then controlled its position (Which practically controls the items' position).
Look at the code :-
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
row = inflater.inflate(R.layout.country_row, parent, false);
}
return row;
}
The country row XML is :--
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="1" >
<TextView
android:id="#+id/nameOfCountry"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:text="Country" />
<CheckBox
android:id="#+id/checkBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.2"
android:gravity="right" />
Now my question is once I check few of the check boxes in the list view and the scroll the list view, many other check boxes are automatically checked....
I know there is problem in getView but I'm not able to figure out where....
Also the problem is solved if I don't reuse the convert view. But that is a dumb idea...
Any thoughts.....
That's because the ListView does recycling of views. Essentially, it reuses some views that go off screen to make the new ones on screen to help with performance. There are 2 ways of dealing with this:
Set the values of the views before you return the row. For example, you would set the nameOfCountry and whether or not the checkbox is checked before the return view line. To do this though, you need to keep track of what is checked.
Don't use the convertview and just inflate a new view every time. This may result in a performance hit, but as long as the list isn't too long it shouldn't be too noticable (at least in my experience). Simply igonre the convertview value
You have to set the state of the checkbox explicitly if you reuse an old View.
Actually it's not ListView but ExpandableListView but it doesn't really matter. Normally I'd do it this way:
<ScrollView >
<LinearLayout>
<ExpandableListView ... />
<TextView ... />
</LinearLayout>
</ScrollView>
..but I've read it's consuming to have ListView inside ScrollView. I tried solving this by modifying ExpandListAdapter (getChildView()) :
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view, ViewGroup parent) {
if(isLastChild){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.extra_info_bottom, null);
}
else{
//get normal item view
}
return view;
}
but this way I can only append childs to groups. How can I append LinearLayout on the bottom outside of any group?
Just add the following code to add the a linear layout at the last listview block.
View footerView = ((LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.listfooter, null, false);
itemlist.addFooterView(footerView);
where the listfooter is the xml file where you can define your layout controlls.
Is there any reason why you want to have the listview inside the scroll?? wouldn't make sense for your layout to do something like:
<LinearLayout>
<ExpandableListView ... />
<ScrollView >
<LinearLayout>
<TextView ... />
</LinearLayout>
</ScrollView>
</LinearLayout>
With this solution you can use weights on the Expandable List and the Scrollview to define how much of the screen you want to use for the List and how much for the linear layout at the bottom.
If you want your linear layout to appear at the very bottom of the list, I would suggest you to use a footer instead of the getView thing.
Anyway, to help with the exact issue you have, I would need to look at the xml definition of what you are trying to inflate.
Try to figure out what object does cause this error and look for it's real className using myObject.getClass().getName(). So you can see what object you really inflated and maybe fix the problem.
A quick question:
In ListView I use this code:
list.addHeaderView(headerView);
How to deal with it when working on gridview?
Thanks.
There is no support for header or footer views with GridView, sorry.
There is a quite good implementation of GridView with header support in Google Photos application, as it's OSS code you can use it as is or take it as a reference for your own implementation:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/4.3_r2.1/com/android/photos/views/HeaderGridView.java
Basic idea is quite simple - WrapperAdapter creates an fake row by increasing number of items by number of columns and then return a header view for the item.
You can use this. The footer appears/hides at the bottom of the grid when you reach/leave the last number of items. It does not actually scroll, but I hardly notice the difference.
In your activity/fragment's onCreate/onCreateView you add an OnScrollListener to the GridView:
....
GridView gridview = (YMAnimatedGridview) v.findViewById(R.id.my_gridview);
gridview.setAdapter(adapter);
final View footerView = mainView
.findViewById(R.id.my_grid_footer_view);
gridview.setOnScrollListener(new GridView.OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (firstVisibleItem + visibleItemCount == totalItemCount) {
// last item in grid is on the screen, show footer:
footerView.setVisibility(View.VISIBLE);
} else if (footerView.getVisibility() != View.GONE) {
// last item in grid not on the screen, hide footer:
footerView.setVisibility(View.GONE);
}
}
#Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
}
});
Your layout should look something like the below. Notice the layout_weight (and layout_height) parameter in the gridview, it is needed to make the correct space for the footer when it becomes visible.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<GridView
android:id="#+id/my_gridview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:columnWidth="160dp"
android:gravity="center_horizontal"
android:horizontalSpacing="12dp"
android:numColumns="auto_fit"
android:layout_weight="1"
android:stretchMode="columnWidth"
android:verticalSpacing="6dp" />
<TextView
android:id="#+id/my_grid_footer_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginBottom="8dp"
android:orientation="horizontal"
android:visibility="gone"
android:text="footer text here" >
</TextView>
</LinearLayout>
Sample code:
GridViewWithHeaderAndFooter gridView = (GridViewWithHeaderAndFooter) v.findViewById(R.id.ly_image_list_grid);
LayoutInflater layoutInflater = LayoutInflater.from(this);
View headerView = layoutInflater.inflate(R.layout.test_header_view, null);
View footerView = layoutInflater.inflate(R.layout.test_footer_view, null);
gridView.addHeaderView(headerView);
gridView.addFooterView(footerView);
Gradle build:
.
compile 'in.srain.cube:grid-view-with-header-footer:1.0.12'
You'd have to use a ListView, then make each row of the list look like it's actually a row of a grid. So if you have a 3 column grid, you'd make a layout for the ListView that looks like 3 columns. You'd then have to modify certain aspects of the Adapter to make it work so that each ListView row actually represents 3 lines of data -- so you know, getCount()/3 type stuff.
How about checking for the "0" index element in your adapter? You can inflate the custom view for the first one.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view==null){
if(position==0){
// ...inflate header view
}else{
// ...do as usual
Haven't tested it, but should work.
You can use AsymmetricGridView and specify headers/footers with a bigger rowSpan so they would take the entire row.
Why don't you change the appearance of the cells for the first rows? if you know how many columns you have, you know how many items will appear in the header = number of columns.It works for me
You could use this library, http://tonicartos.github.io/StickyGridHeaders/
which allows you to create headers that are sticky (for grouping the list and keeping the header visible for the current group). You can turn off the sticky feature as well.
There is a way to accomplish the desired functionality WITHOUT using a library or anything.
EDIT: Just borrow the HeaderGridView Implementation by google, see Here
You could also customize it for footer. The below suggestion is just too complicated and required more tweaking.
Without going into specific details, all you need to do is this.
1) Subclass GridView
2) override onScrollChanged
3) Calculate the offset everytime it scrolls
4) Set the parentView(view that contains the headerView and gridview) translation y to -Offset.(view.setTranslationY(-offset). Also have an if statement to that once it reaches a certain offset it would stop scrolling.
5) obviously you want to structure this well so your gridview can have a method like attachToGridview(View view). I have a complete implementation of this which works.
See Scroll offset of GridView for help on getting offset since GridView has a bug were the views get recycled.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:orientation="vertical">
<com.test.Breadcrumbs android:layout_width="fill_parent" android:layout_height="100dp" />
<GridView
android:id="#+id/grid"
android:numColumns="auto_fit"
android:gravity="center"
android:stretchMode="columnWidth"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp">
</GridView>
</LinearLayout>
and Breadcrumbs:
public class Breadcrumbs extends LinearLayout {
public Breadcrumbs(final Context context, final AttributeSet attrs) {
super(context, attrs);
final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutView = inflater.inflate(R.layout.breadcrumbs, this, true);
works fine, scroll for grid works as well.