I have this items.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:focusable="true"
android:clickable="true"
android:background="?android:attr/selectableItemBackground"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="#+id/colorPreview" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="#android:style/TextAppearance.Large"
android:textColor="#android:color/white"
android:id="#+id/colorName" />
</RelativeLayout>
When I use it separately, the selectableItemBackground animates when I click the view. But when I use it for the items in a RecyclerView, the effect on click does not happen anymore. How can I fix this?
PS: this is the listener on the RecyclerView, if it is relevant:
public ColorListOnItemTouchListener(Context context, OnItemClickListener clickListener) {
mClickListener = clickListener;
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public void onLongPress(MotionEvent e) {
if(childView != null && mClickListener != null) {
mClickListener.onItemLongPress(childView, index);
}
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
if(childView != null && mClickListener != null) {
mClickListener.onItemClick(childView, index);
}
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
}
Thank you!
Edit:
public class ColorsCursorAdapter extends RecyclerView.Adapter<ColorsCursorAdapter.ViewHolder> {
private static final int layout = R.layout.color_item;
private Cursor mCursor;
public ColorsCursorAdapter(Cursor c) {
super();
this.mCursor = c;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
TextView name = (TextView) v.findViewById(R.id.colorName);
ImageView image = (ImageView) v.findViewById(R.id.colorPreview);
return new ViewHolder(v, name, image);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
mCursor.moveToPosition(position);
int color = mCursor.getInt(mCursor.getColumnIndex(ColorItem.COLUMN_COLOR));
holder.colorName.setText(Utils.getColorString(color));
holder.colorPreview.setImageDrawable(new ColorDrawable(color));
}
#Override
public int getItemCount() {
if(mCursor != null) {
return mCursor.getCount();
}
return 0;
}
public void swapCursor(Cursor c) {
mCursor = c;
notifyDataSetChanged();
}
public Cursor getCursor() {
return mCursor;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView colorName;
public ImageView colorPreview;
public ViewHolder(View root, TextView colorName, ImageView colorPreview) {
super(root);
root.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//
}
});
this.colorName = colorName;
this.colorPreview = colorPreview;
}
}
}
And the adapter is created with:
colorList.setLayoutManager(new LinearLayoutManager(this));
adapter = new ColorsCursorAdapter(null);
colorList.setAdapter(adapter);
Something that was not mentioned in other answers: setting android:clickable="true" is required in order to make the animations work when there is no OnClickListener attached to the view.
Instead of setting it as background, I set it as foreground, it works. Hope it would helpful.
android:foreground="?attr/selectableItemBackground"
In your items.xml, set FrameLayout as the root layout and set selectableItemBackground for FrameLayout. It works for me, but I do not know why.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:selectableItemBackground">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- your code -->
</RelativeLayout>
</FrameLayout>
View that you want to have ripple effect has to be clickable. You can either do that by specifying android:clickable="true" in XML file or setting onClickListener programmatically. When you are using this on item inside RecyclerView and you are using Kotlin set onClickListener on "itemView" from ViewHolder class.
I have seen the same behavior. On my side it was related to the fact that when I clicked a list item within a fragment and directly replacing that fragment with another, the animation hadn't no time to be shown. As soon as I removed the fragment replacement, I could see the animation.
#Override
public boolean onSingleTapUp(MotionEvent e) {
if(childView != null && mClickListener != null) {
mClickListener.onItemClick(childView, index);
}
return true;
}
Return false might fix this issue. For me, I was overriding onTouch on my button and returning true at the end. Returning false enabled the touch animation to happen from setting:
android:background="?attr/selectableItemBackground"
In my case the problem was caused by DividerItemDecoration. As soon as I got rid of that, it started to work. I suggest to put a line at the end of xml file for each item in RecyclerView, it's much easier to customize and you won't run into these kinds of problems.
No one of the previous solutions worked for me.
Finally I had to implement my own drawable for that, and its work like the provided by Android.
Create a new drawable:
res/drawable/selectable_item_background.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/colorAccent">
<item
android:id="#android:id/mask"
android:drawable="#android:color/white" />
</ripple>
Use it in you cell:
Just like you used the Android drawable
android:background="#drawable/selectable_item_background"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/selectable_item_background"
android:clickable="true"
android:orientation="vertical">
...
For more info, or implementation for versions before API 21. Take a look to this post.
Related
I am using for loop, and I have around 20 items displaying in recycler view! Now how do I hide/show image view as for loop runs... Here tts is working fine, but when i try to show/hide using this its not happening..
Presently with below code, once for loops ends the entire imageview is affecting, i want it a row wis(show current(image1) & hide previous imageview(image) )
I am calling this method from main Activity class, but the imageview show or hide not happening
activity
......
......
private void ConvertTextToSpeech() {
// TODO Auto-generated method stub
//items.forEach( Multiples obj -> System.out.println());
int z=0;
View holder=null; ImageView imageView=null;ImageView imageView1=null;
for (Multiples p : items) {
if(z>0){
holder = recyclerView.findViewHolderForAdapterPosition(z).itemView;
holder.findViewById(R.id.image).setVisibility(View.VISIBLE);
holder.findViewById(R.id.image1).setVisibility(View.INVISIBLE);
}
if(z < items.size()) {
holder = recyclerView.findViewHolderForAdapterPosition(z).itemView;
holder.findViewById(R.id.image).setVisibility(View.INVISIBLE);
holder.findViewById(R.id.image1).setVisibility(View.VISIBLE);
}
text = p.first + " " + p.getSecond() + " Za "+p.getResult()+".";
tts.speak(text, TextToSpeech.QUEUE_ADD, null);
z++;
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.balysv.materialripple.MaterialRippleLayout
android:id="#+id/lyt_parent"
style="#style/RippleStyleBlack"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:focusable="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#color/grey_10" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="horizontal"
android:textAlignment="gravity" >
<View
android:layout_width="#dimen/spacing_large"
android:layout_height="wrap_content" />
<ImageView
android:id="#+id/image"
android:layout_width="35dp"
android:layout_height="35dp"
android:background="#android:color/transparent"
android:src="#drawable/ic_arrow_right" />
<ImageView
android:id="#+id/image1"
android:layout_width="51dp"
android:layout_height="35dp"
android:background="#android:color/transparent"
android:src="#drawable/ic_arrow_right"
android:visibility="invisible" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="#dimen/spacing_middle"
android:paddingTop="#dimen/spacing_middle">
<TextView
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="#dimen/spacing_middle"
android:layout_marginRight="#dimen/spacing_middle"
android:fontFamily="sans-serif-condensed"
android:gravity="center|left"
android:text="36"
android:textAppearance="#style/TextAppearance.AppCompat.Medium"
android:textColor="#color/grey_80"
android:textSize="30sp"
/>
</LinearLayout>
<View
android:layout_width="#dimen/spacing_large"
android:layout_height="match_parent" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#color/grey_10" />
</LinearLayout>
</com.balysv.materialripple.MaterialRippleLayout>
</RelativeLayout>
adapter
public class AdapterListAnimation extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Multiples> items = new ArrayList<>();
private Context ctx;
private OnItemClickListener mOnItemClickListener;
private int animation_type = 0;
.........
.........
You need just add field in your adapter where you will save currently active item. Then call notifyDataSetChanged.
Also you should update your onBindViewHolder like this:
private int currentPosition = -1;
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
holder.textView.setText(items.get(position));
if (currentPosition == position) {
holder.img1.setVisibility(View.INVISIBLE);
holder.img2.setVisibility(View.VISIBLE);
} else {
holder.img1.setVisibility(View.VISIBLE);
holder.img2.setVisibility(View.INVISIBLE);
}
}
public void setCurrentPosition(int currentPosition) {
this.currentPosition = currentPosition;
}
I've created a test project to show how it can be implemented. Here you can see how it works. Here is my github repository.
You should not directly change the item view state inside the adapter with the following:
holder = recyclerView.findViewHolderForAdapterPosition(z).itemView;
holder.findViewById(R.id.image).setVisibility(View.VISIBLE);
holder.findViewById(R.id.image1).setVisibility(View.INVISIBLE);
instead, you need to use a specific method to tell the RecyclerView adapter that you need to change a state of an item view.
Be noted, that RecylerView naturally (in programmatically way) will recyler it's previous item whenever it needs to draw another item. So, you need to keep the state of each item whether using a specific variable inside your model or using a variable to hold each item state.
In case that you don't want to change your model, you can use SparseBooleanArray to hold the state. You can do something like this in your Adapter:
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Multiples> mItems;
// We use this to hold the image state.
private SparseBooleanArray mImageStates;
public YourAdapter(List<Multiples> items) {
this.mItems = items;
mImageStates = new SparseBooleanArray();
...
}
#Override
public YourAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
...
return viewHolder;
}
#Override
public void onBindViewHolder(ContactsAdapter.ViewHolder viewHolder, int position) {
int itemPosition = viewHolder.getAdapterPosition();
Multiples item = mItems.get(itemPosition);
// Load the state by the previous saved state
// default value is false.
if(mImageStates.get(itemPosition)) {
// show the images or something.
} else {
// hide the images or something.
}
}
public void setState(int position, boolean isVisible) {
mImageStates.put(position, isVisible);
}
}
now you can change the state item visibility with:
yourAdapter.setState(yourItemPosition, true);
then notify the adapter about the change with:
yourAdapter.notifyItemChanged(yourItemPosition);
or reset all if you have change many items with:
yourAdapter.notifyDataSetChanged();
you have to write a logic on onbindholder then.
onBindHolder(ViewHolder holder, int position){
if(position %2==0)
imageview.setVisibility(View.Visible);
else
imageview.setVisibility(View.InVisible);
}
Finally Got the Solution
I have added two parameters in your Multiples. In your loop when a condition is true, I am setting variable according to you have done with Image view. after I am notifying RecyclerView. so, a callback will come on onBindViewHolder. there I have added conditions for show/hide Image view.
suppose left Arrow is image & right Arrow is image1
NOTE: Android Default TextToSpeech does not provide any event listener for complete. So, I have tried a new library that is well maintained and provide Speech recognition and Text to speech functionality.
Gradle:
implementation 'net.gotev:speech:1.4.0'
Initialization:
To start using the library, you have to initialize it in your Activity.
public class YourActivity extends Activity {
private count = 0;
private maxCount = 0;
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
Speech.init(this, getPackageName());
maxCount = items.size();
}
#Override
protected void onDestroy() {
// prevent memory leaks when activity is destroyed
Speech.getInstance().shutdown();
}
}
public class Multiples implements Serializable {
private Boolean isShowimage = false;
private Boolean isShowimage1 = false;
public Boolean getShowimage() {
return isShowimage;
}
public void setShowimage(Boolean showimage) {
isShowimage = showimage;
}
public Boolean getShowimage1() {
return isShowimage1;
}
public void setShowimage1(Boolean showimage1) {
isShowimage1 = showimage1;
}
}
private void ConvertTextToSpeech() {
if (count < maxCount) {
reset();
String text = items.get(count).first + " " + items.get(count).getSecond() + " Za " + items.get(count).getResult() + ".";
Speech.getInstance().say(text, new TextToSpeechCallback() {
#Override
public void onStart() {
Log.i("speech", "speech started");
items.get(count).setShowimage(false);
items.get(count).setShowimage1(true);
adapter.notifyDataSetChanged();
}
#Override
public void onCompleted() {
Log.i("speech", "speech completed");
ConvertTextToSpeech();
count++;
}
#Override
public void onError() {
Log.i("speech", "speech error");
}
});
}
}
private void reset() {
for (Multiples p : items) {
p.setShowimage(true);
p.setShowimage1(false);
}
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Multiples item = items.get(position);
if (item.getShowimage()) {
holder.image.setVisibility(View.VISIBLE);
} else {
holder.image.setVisibility(View.INVISIBLE);
}
if (item.getShowimage1()) {
holder.image1.setVisibility(View.VISIBLE);
} else {
holder.image1.setVisibility(View.INVISIBLE);
}
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView,imageView1;
public ViewHolder(View view) {
super(view);
this.imageView = (ImageView) view.findViewById(R.id.imageView);
this.imageView1 = (ImageView) view.findViewById(R.id.imageView1);
}
}
I have gotten a pretty good hang of Android Data Binding but I am stuck on the last thing in my app that I have not been able to bind with data binding yet. I have a RecyclerView on which I have implemented a custom touch listener to handle short clicks and long clicks. I am just not sure WHAT exactly I am supposed to bind. Is it even possible to use data binding for an onItemTouchListener? I will paste the code I have, and hopefully somebody can point me in the right direction by telling me what I should be trying to bind, or if there is even a point.
The touch listener:
public class RecyclerViewTouchListener implements RecyclerView.OnItemTouchListener
{
private RecyclerViewClickListener clickListener;
private GestureDetector gestureDetector;
public RecyclerViewTouchListener(Context context, final RecyclerView recycleView, final RecyclerViewClickListener cl)
{
this.clickListener = cl;
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener()
{
#Override
public boolean onSingleTapUp(MotionEvent e)
{
return true;
}
#Override
public void onLongPress(MotionEvent e)
{
View child = recycleView.findChildViewUnder(e.getX(), e.getY());
if (child != null && cl != null)
{
cl.onLongClick(child, recycleView.getChildAdapterPosition(child));
}
}
});
}
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null && clickListener != null && gestureDetector.onTouchEvent(e))
{
clickListener.onClick(child, rv.getChildAdapterPosition(child));
}
return false;
}
#Override
public void onTouchEvent(RecyclerView rv, MotionEvent e)
{
}
#Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)
{
}
}
The click listener:
public interface RecyclerViewClickListener
{
void onClick(View view, int position);
void onLongClick(View view, int position);
}
The current xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="activity"
type="com.redacted.ListActivity"/>
</data>
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.redacted.ListActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="#+id/listRv"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
<com.github.clans.fab.FloatingActionButton
xmlns:fab="http://schemas.android.com/apk/res-auto"
android:id="#+id/addItemFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{activity::fabClicked}"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_add"
fab:fab_colorNormal="#color/colorAccent"
fab:fab_colorPressed="#color/colorAccentLight"
fab:fab_size="normal"/>
</android.support.design.widget.CoordinatorLayout>
</layout>
My activity:
public class ListActivity extends AppCompatActivity
{
private OrderedRealmCollection<MyObject> allItems;
private Realm realm;
private ActivityListBinding b;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
b = DataBindingUtil.setContentView(this, R.layout.activity_list);
b.setActivity(this);
setSupportActionBar(b.toolbar);
realm = Realm.getDefaultInstance();
CreateViewModel();
b.listRv.setHasFixedSize(true);
b.listRv.setAdapter(new ListAdapter(allItems, true));
b.listRv.setLayoutManager(new LinearLayoutManager(this));
b.listRv.addOnItemTouchListener(new RecyclerViewTouchListener(this,
b.listRv, new RecyclerViewClickListener()
{
#Override
public void onClick(View view, final int position)
{
//unimportant code
}
#Override
public void onLongClick(View view, int position)
{
//unimportant code
}
}));
}
private void CreateViewModel()
{
ListViewModel vm = new ListViewModel(realm);
allItems = vm.getAllItems();
}
#Override
protected void onDestroy()
{
super.onDestroy();
b.listRv.setAdapter(null);
realm.close();
}
public void fabClicked(View view)
{
//unimportant code
}
}
My goal is to have OnClick and OnLongClick bound in the XML somehow. If there is a better way to handle short and long press on RecyclerView items, you can tell me I am doing things the wrong way too.
My goal is to have OnClick and OnLongClick bound in the XML somehow
I would go for two different interface, if I were in you. But that's just a matter of taste. To bind it you need a binding adapter:
#SuppressWarnings("unchecked")
#BindingAdapter("onItemClickListener")
public static <T> void setOnItemClickListener(RecyclerView recyclerView, RecyclerViewClickListener clickListener) {
if (recyclerView instanceof RecyclerViewTouchListener) {
((RecyclerViewTouchListener) recyclerView).setItemClickListener(clickListener);
}
}
and, of course, your RecyclerViewTouchListener, should declare a setter for RecyclerViewClickListener. And in your layout, you should have something like
<your.package.to.RecyclerViewTouchListener
app:onItemClickListener="#{yourViewModel.getRecyclerViewClickListener()}"
I'm a new developer. The space between preferences is big, looks not good. I have a defined preference layout file. How to set them closer? Thanks a lot!
preferences.xml
<PreferenceCategory
android:title="#string/title_user_profile"
android:key = "#string/preference_key_category_usersetting"
android:layout="#layout/preference_layout">
<Preference android:title="#string/user_email_address"
android:key="#string/preference_key_email"
android:summary="#string/summary_joined_from"
android:editable="false"
android:clickable="false"
android:lineSpacingExtra="-4dp"/>
<com.ipretii.app.activity.setting.NamePreference
android:key="#string/preference_key_username"
android:summary="Type in your user name"
android:lineSpacingExtra="-4dp"/>
<com.ipretii.app.activity.setting.BirthYearPickerPreference
android:title="Birth Year"
android:key="#string/preference_key_birthyear"
android:lineSpacingExtra="-4dp"/>
</PreferenceCategory>
preference_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="#+android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/text_size_title"
android:textColor="#color/colorPrimary"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"/>
<TextView android:id="#android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/text_size_subheading"
android:textColor="#color/colorSubHeadingText"
android:paddingRight="16dp"/>
</LinearLayout>
Just putting a simple disabled Preference tag would work:
<Preference
android:enabled="false"
app:title="" />
While I cannot claim to like the solution, I did solve this with the following code. I tested this with DialogPreference, CheckBoxPreference, and ListPreference. I did not try this with any Preferences with custom layouts.
In my SettingsFragment, I created a class that wraps a ListAdapter and sets the vertical padding for all of its children to 0:
// An Adapter that wraps another adapter, but updates all child Views to have zero vertical padding
public static class ListAdapterWrapper implements ListAdapter
{
private ListAdapter wrappedAdapter;
public ListAdapterWrapper(ListAdapter wrappedAdapter)
{
super();
this.wrappedAdapter = wrappedAdapter;
}
#Override
public android.view.View getView(int i, android.view.View view, android.view.ViewGroup viewGroup)
{
View newView = wrappedAdapter.getView(i, view, viewGroup);
if (newView != null && newView instanceof LinearLayout)
{
LinearLayout layout = (LinearLayout)newView;
// set the top and bottom padding to 0 for each child
int count = layout.getChildCount();
for(int ii=0; ii<count; ii++)
{
View v = layout.getChildAt(ii);
v.setPadding(v.getPaddingLeft(), 0, v.getPaddingRight(), 0);
}
}
return newView;
}
#Override public boolean areAllItemsEnabled() { return wrappedAdapter.areAllItemsEnabled(); }
#Override public boolean isEnabled(int i) { return wrappedAdapter.isEnabled(i); }
#Override public void registerDataSetObserver(android.database.DataSetObserver dataSetObserver) { wrappedAdapter.registerDataSetObserver(dataSetObserver); }
#Override public void unregisterDataSetObserver(android.database.DataSetObserver dataSetObserver) { wrappedAdapter.unregisterDataSetObserver(dataSetObserver); }
#Override public int getCount() { return wrappedAdapter.getCount(); }
#Override public java.lang.Object getItem(int i) { return wrappedAdapter.getItem(i); }
#Override public long getItemId(int i) { return wrappedAdapter.getItemId(i); }
#Override public boolean hasStableIds() { return wrappedAdapter.hasStableIds(); }
#Override public int getItemViewType(int i) { return wrappedAdapter.getItemViewType(i); }
#Override public int getViewTypeCount(){ return wrappedAdapter.getViewTypeCount(); }
#Override public boolean isEmpty() { return wrappedAdapter.isEmpty(); }
}
And I overwrite the ListView's adapter with a ListAdapterWrapper:
#Override
public void onActivityCreated(android.os.Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
ListView list = (ListView)getView().findViewById(android.R.id.list);
if (list != null)
{
// Overwrite the adapter so we can decrease the padding between Settings items
list.setAdapter(new ListAdapterWrapper(list.getAdapter()));
}
}
I think you can adjust the android:padding attributes for your taste.
For example if you want to set them vertically closer, you can set android:paddingTop to 8dp or whatever.
I have an activity with an AutoCompleteTextView and a button, and below it, a hidden RecyclerView, that starts empty (with no rows).
With the AutoCompleteTextView, I select an object and what I want is, when I click the button, to add that object to the RecyclerView(and turn the visibility on for the recycler).
So far, I've managed to add the object to the recycler's DataSet, but it won't show any row on notifyDataSetChanged().
The layout:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:id="#+id/ib_add"
android:src="#drawable/ic_add_black"
android:onClick="onAddClick"/>
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/actv_input"
android:layout_toLeftOf="#+id/ib_add"
android:layout_toStartOf="#+id/ib_add"
android:hint="#string/name_hint"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>
The Activity (just the relevant functions):
#Bind(R.id.rv) RecyclerView rv;
ArrayList<Ing> ings = new ArrayList<>();
ArrayList<Ing> selectedIngs = new ArrayList<>();
private void setupRecyclers() {
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new RecyclerViewAdapter(selectedIngs));
}
public void onAddClick(View view) {
Ing ing = ings.get(selectedPosition);
rv.setVisibility(View.VISIBLE);
selectedIngs.add(ing);
((RecyclerViewAdapter)rv.getAdapter()).addIng(ing);
}
The RecyclerViewAdapter:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RViewHolder> {
ArrayList<Ing> ings;
public RecyclerViewAdapter(ArrayList<Ing> ings) {
this.ings = ings;
}
#Override
public RViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View row = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_ing, parent, false);
return new RViewHolder(row);
}
#Override
public void onBindViewHolder(RViewHolder holder, int position) {
Ing ing = ings.get(position);
holder.bindToView(ing);
}
#Override
public int getItemCount() {
return ings.size();
}
public void addIng(Ing ing) {
ings.add(ing);
notifyDataSetChanged();
}
public void setDataSet(ArrayList<Ing> ings) {
this.ings = ings;
notifyDataSetChanged();
}
}
The RecyclerViewHolder:
public class RViewHolder extends RecyclerView.ViewHolder {
Ing ing;
ImageView ivIcon;
TextView tvName;
ImageButton ibRemove;
TextView tvPercentage;
EditText etPercentage;
public RViewHolder(View row) {
super(row);
bindFields(row);
}
private void bindFields(View view) {
ivIcon = (ImageView) view.findViewById(R.id.iv_icon_type);
tvName = (TextView) view.findViewById(R.id.tv_ing);
ibRemove = (ImageButton) view.findViewById(R.id.btn_remove);
tvPercentage = (TextView) view.findViewById(R.id.tv_percentage);
etPercentage = (EditText) view.findViewById(R.id.et_percentage);
}
public void bindToView(Ing ing){
this.ing = ing;
tvName.setText(ing.getName());
ivIcon.setImageResource(R.drawable.icon);
tvName.setVisibility(View.VISIBLE);
ivIcon.setVisibility(View.VISIBLE);
ibRemove.setVisibility(View.VISIBLE);
tvPercentage.setVisibility(View.VISIBLE);
etPercentage.setVisibility(View.VISIBLE);
}
}
What am I missing?
Thanks!
Solved!
The first part, the recycler not showing even with an element inside, was fixed by the 23.2.0 support library's update, that introduced auto measurement for recyclerviews. Before that, if you had a recycler within a relative within a nested, it did some funny business and never initialized the rows inside the recycler.
The second part, the button not adding the row, was because even if you have some onclick event set in your image button, it looks that you have to put android:clickable = true or else it won't work.
I am trying to find a way to implement a standing table for a sports app (like NBA Game Time standings), with a fixed header, fixed first column and a footer. I searched a bit on how to get it, but the best shot was this project (https://github.com/InQBarna/TableFixHeaders) but it uses its own view instead of Recycler of GridView. Do anyone knows something like this or knows how I can start with it (Adapter or LayoutManager)?
Edit (adding images)
After testing and searching a lot, I implemented by my own, combining a ListView with inner HorizontalScrollViews.
First, I extended HorizontalScrollView to report me the scroll event, adding a listener:
public class MyHorizontalScrollView extends HorizontalScrollView {
private OnScrollListener listener;
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (listener != null) listener.onScroll(this, l, t);
}
public void setOnScrollListener(OnScrollListener listener) {
this.listener = listener;
}
public interface OnScrollListener {
void onScroll(HorizontalScrollView view, int x, int y);
}
}
Then, I created my layout with a LinearLayout, containing my header and a ListView for my Activity (or Fragment, if it's your need).
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/header"
layout="#layout/header" />
<ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Each item of my list is a LinearLayout, with a TextView (the fixed column) and a HorizontalScrollView. The layout of both header and lines are the following:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
style="?android:attr/textAppearanceMedium"
android:background="#f00"
android:minWidth="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<View
android:layout_width="1px"
android:layout_height="match_parent"
android:background="#android:color/black" />
<net.rafaeltoledo.example.MyHorizontalScrollView
android:id="#+id/scroll"
android:scrollbars="none"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- Childs, or columns -->
</LinearLayout>
</net.rafaeltoledo.example.MyHorizontalScrollView>
</LinearLayout>
The trick is to scroll all With a help of a EventBus (I used the GreenRobot's one) to fire the horizontal scroll event and move all scrollers as one. My event object contains the same data from the listener class (maybe I can use the listener object itself?)
public static class Event {
private final int x;
private final int y;
private final HorizontalScrollView view;
public Event(HorizontalScrollView view, int x, int y) {
this.x = x;
this.y = y;
this.view = view;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public HorizontalScrollView getView() {
return view;
}
}
The list adapter class receives a listener to set in the HorizontalScrollView of each item.
public static class Adapter extends BaseAdapter {
private final Context context;
private MyHorizontalScrollView.OnScrollListener listener;
public Adapter(Context context, MyHorizontalScrollView.OnScrollListener listener) {
this.context = context;
this.listener = listener;
}
#Override
public int getCount() {
return 30;
}
#Override
public Object getItem(int position) {
return new Object();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.header, parent, false);
MyHorizontalScrollView scroll = (MyHorizontalScrollView) convertView.findViewById(R.id.scroll);
scroll.setOnScrollListener(listener);
}
return convertView;
}
public Context getContext() {
return context;
}
}
Before continue, I registered MyHorizontalScrollView to EventBus, adding EventBus.getDefault().register(this) to each version of constructor, and added the receiver method to it:
public void onEventMainThread(MainActivity.Event event) {
if (!event.getView().equals(this)) scrollTo(event.getX(), event.getY());
}
that will scroll to the received position, if was not itself that fired the scroll event.
And finally, I setted up everything in the onCreate() method of my Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
MyHorizontalScrollView.OnScrollListener listener = new MyHorizontalScrollView.OnScrollListener() {
#Override
public void onScroll(HorizontalScrollView view, int x, int y) {
Log.d("Scroll Event", String.format("Fired! %d %d", x, y));
EventBus.getDefault().post(new Event(view, x, y));
}
};
ListView listView = (ListView) findViewById(R.id.list);
ViewGroup header = (ViewGroup) findViewById(R.id.header);
header.getChildAt(0).setBackgroundColor(Color.WHITE);
header.setBackgroundColor(Color.BLUE);
((MyHorizontalScrollView) header.findViewById(R.id.scroll)).setOnScrollListener(listener);
listView.setAdapter(new Adapter(this, listener));
listView.addFooterView(getLayoutInflater().inflate(R.layout.footer, listView, false));
}
(Please ignore some weird coloring, it's for better viewing what's happening).
And ta-daa, here is the desired result!
You can implement a layout like that like this:
<LinearLayout
...
android:orientation="vertical">
<TextView
... />
<com.example.TableHeaderView
... />
<RecyclerView
... />
</LinearLayout>
Only the RecyclerView will scroll, leaving your title text view and table header at the top of the screen.
From your images I would suggest considering using a library such as StickyGridHeaders. It extends GridView and can have custom 'header' views.
Alternatives are StickyListHeaders or HeaderListView but focus more on the ListView
Edit:
On further investigation, the example provided in this tutorial seems to match your requirements