I am working on the TV app and trying to use leanback support library as much as possible. The issue I ran into is that I am trying to add a View on top of the ImageView in ImageCardView. I would like to have my custom View centered on top of that image.
I've tried simply adding the View on 0 index but it just pushes the image down and sits on top of it. I see the ImageCardView is FrameLayout but it seems that FrameLayout is changed so all the children are vertically aligned.
I've also tried to fetch a parent of ImageCardView, which is ShadowOverlayContainer, and add the View there but it still doesn't show.
Example for regular card:
What I am trying to accomplish:
Any suggestions how to add that View? Thank you.
I think this works.
onBindViewHolder(){
.
..
...
int count = ((ViewGroup)((ImageCardView) viewHolder.view).getMainImageView().getParent().getParent()).getChildCount();
((ViewGroup)((ImageCardView)
viewHolder.view).getMainImageView().getParent().getParent()).addView(xxx, count);
}
Since API Level 18 (JELLY_BEAN_MR2) you can use a ViewOverlay
https://developer.android.com/reference/android/view/ViewOverlay
https://developer.android.com/reference/android/view/View#getOverlay()
get a Drawable drawable = context.getDrawable(R.drawable.sample)
set its Bounds drawable.setBounds(0, 0, 50, 50)
add an Overlay view.getOverlay().add(drawable)
public class ImageCardViewPresenter extends Presenter {
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
ImageCardView imageCardView = new ImageCardView(parent.getContext());
imageCardView.setMainImageDimensions(100, 100);
return new ImageCardViewHolder(imageCardView);
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
Model model = (Model) item;
ImageCardView imageCardView = (ImageCardView) viewHolder.view;
Context context = imageCardView.getContext();
imageCardView.setTitleText(model.getTitle());
imageCardView.setMainImage(context.getDrawable(model.getImageId());
Drawable badge = context.getDrawable(model.getBadgeId());
badge.setBounds(0, 0, 20, 20);
imageCardView.getOverlay().clear();
imageCardView.getOverlay().add(badge);
}
#Override
public void onUnbindViewHolder(ViewHolder viewHolder) {
ImageCardView imageCardView = (ImageCardView) viewHolder.view;
imageCardView.setMainImage(null);
imageCardView.getOverlay().clear();
}
public static class ImageCardViewHolder extends ViewHolder {
public ImageCardViewHolder(View view) {
super(view);
}
}
}
public class Model {
private String title;
#DrawableRes
private int imageId;
#DrawableRes
private int badgeId;
// getters and setters
}
Related
I'm creating a list with a RecyclerView.
Every list item is a post from a user (Right now hard coded).
The background for every post is loaded from a layer-list XML file in the drawable folder.
Everything is working as intended with the texts etc. but I'm trying to change the background color programmatically. It changes the background color for every item, except for the first item, and I cannot figure out why.
The first item always gets the background color specified by the solid color of the shape inside the item called shape_background in the XML file, so it is not changed, but the following items get the color #ff22ff.
This is the implementation of the adapter:
class PostListAdapter extends RecyclerView.Adapter<PostListAdapter.PostViewHolder>{
private LayoutInflater inflater;
private List<PostRow> data = Collections.emptyList();
PostListAdapter(Context context, List<PostRow> data) {
inflater = LayoutInflater.from(context);
this.data = data;
}
#Override
public void onBindViewHolder(PostViewHolder holder, int position) {
PostRow current = data.get(position);
holder.text.setText(current.text.toUpperCase());
holder.time.setText(current.time.toUpperCase());
holder.answers.setText(current.answers.toUpperCase());
try {
// "#ff22ff" will be changed to current.color, unique color for every post
// That string is parsed from a JSON request, hence the try-catch.
int color = Color.parseColor("#ff22ff");
holder.shape.setColor(color);
} catch (Exception e){
e.printStackTrace();
}
}
#Override
public PostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.post_row, parent, false);
return new PostViewHolder(view);
}
#Override
public int getItemCount() {
return data.size();
}
class PostViewHolder extends RecyclerView.ViewHolder {
TextView text;
TextView time;
TextView answers;
GradientDrawable shape;
PostViewHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(R.id.text);
time = (TextView) itemView.findViewById(R.id.time);
answers = (TextView) itemView.findViewById(R.id.answers);
LayerDrawable layers = (LayerDrawable) ContextCompat.getDrawable(itemView.getContext(), R.drawable.bubble);
shape = (GradientDrawable) (layers.findDrawableByLayerId(R.id.shape_background));
}
}
}
Why is the background of the first item not changed, but the texts are?
Thank you in advance!
On your onBindViewHolder, get your view(from holder) for that you need to change background color & get its current background(drawable bubble, that you already set in XML attribute)
LayerDrawable layerDrawable = (LayerDrawable) yourView.getBackground().getCurrent();
GradientDrawable gradientDrawable = (GradientDrawable) layerDrawable.findDrawableByLayerId(R.id. shape_background).getCurrent();
// set you color based on position
gradientDrawable.setColor(Color.parseColor("#ff22ff"));
you have called holder.shape.setColor(color);
but the holderView didn't refresh.
maybe you should call
holder.itemView.setBackgroundDrawable(
ContextCompat.getDrawable(itemView.getContext(), R.drawable.bubble))
to refresh the holderView
You have two different issues here.
First - you change drawable itself but not view background. It is solved by liubaoyua and Vignesh Sundar answers.
Second - you change R.drawable.bubble statically. Android can cache and reuse it even after mutation. It can lead to another issues when you start use different colors for different items.
To solve second issue you must call android.graphics.drawable.Drawable#mutate every time before firstly mutate it.
By default, all drawables instances loaded from the same resource share a common state; if you modify the state of one instance, all the other instances will receive the same modification.
So it should looks something like this:
class PostViewHolder extends RecyclerView.ViewHolder {
TextView text;
TextView time;
TextView answers;
LayerDrawable layers;
GradientDrawable shape;
PostViewHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(R.id.text);
time = (TextView) itemView.findViewById(R.id.time);
answers = (TextView) itemView.findViewById(R.id.answers);
layers = (LayerDrawable) ContextCompat
.getDrawable(itemView.getContext(), R.drawable.bubble)
.mutate();
shape = (GradientDrawable) layers.findDrawableByLayerId(R.id.shape_background);
itemView.setBackground(layers);
}
void setColor(Color color) {
shape.setColor(color);
itemView.setBackgroundDrawable(layers);
}
}
May be can be little simplified by moving .mutate() from layer to shape.
I am writing a photo picker for Facebook inside my app, i have a recyclerview with a grid layout and i want to prevent for scrolling up, i was able to do this by using scrollToPosition and this works but not the way i want
Problem
When i click in a photo on the 2 row that row jumps to the top and becomes the number 1 visible row, if i click the 3 row the samething happens.
I don't want the recycler to move if the view is visible it should remain the same, so if i click on a a photo that is on the last visible row i want the scroll to stay the same, i don't want it to make the last row the first.
Tries to solve it
I tried several things to fix this, i tried calling setNestedScrollingEnabled i followed this How to disable RecyclerView scrolling?
public static void onItemClick(int position){
//picker.setNestedScrollingEnabled(false);
for(int k = 0; k<photoBag.size();k++) {
if(k == position)
photoBag.set(position, new PhotoBag(photoBag.get(position).getPhoto(), true)); //Here im marking the photo to selected
else
photoBag.set(k, new PhotoBag(photoBag.get(k).getPhoto(), false));//Here im setting unselecting all the other photos
}
picker.setAdapter(adapter);
adapter.notifyDataSetChanged();
picker.scrollToPosition(position);
//Log.d("FacebookPicker", "position " + grid.findFirstCompletelyVisibleItemPosition());
//picker.setNestedScrollingEnabled(true);
}
I thought that maybe disabling the scroll would lock the recyclerview on the corrent position but it didn't jumps right up.
I also tried getting the Vertical offset and set it after calling notifyDataSetChange but i can't find a way to set the offset programmatically
EDIT
Adapter
class PickerAdapter extends RecyclerView.Adapter<PickerAdapter.PickerAdapterHolder> {
public final String TAG = "PickerAdapter";
private ArrayList<PhotoBag> photoBag;
private Context context;
private OnClickListener onClickListener;
class PickerAdapterHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView photo;
ImageView imageBorder;
PickerAdapterHolder(View view) {
super(view);
photo = (ImageView) view.findViewById(R.id.photoItem);
photo.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.photoItem:
FacebookPhotoPicker.onItemClick(getAdapterPosition()); //i know that there are better ways to get the clicked item from other class but since im still debuging i don't need to worry about performace i just need it to work
break;
}
}
}
PickerAdapter(Context context, ArrayList<PhotoBag> itemList) {
this.photoBag = itemList;
this.context = context;
}
#Override
public PickerAdapterHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.facebook_picker_item, null);
return new PickerAdapterHolder(layoutView);
}
#Override
public void onBindViewHolder(final PickerAdapterHolder holder, final int position) {
if(photoBag.get(position).isSelected()){
int border = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, context.getResources().getDisplayMetrics()));
Bitmap photo = photoBag.get(position).getPhoto();
photo = Bitmap.createScaledBitmap(photo,photo.getWidth() - (border*2), photo.getHeight() - (border*2), false);
photo = addWhiteBorder(photo,border);
holder.photo.setImageBitmap(photo);
}else {
holder.photo.setImageBitmap(photoBag.get(position).getPhoto());
}
}
#Override
public int getItemCount() {
return this.photoBag.size();
}
private Bitmap addWhiteBorder(Bitmap bmp, int borderSize) {
Bitmap bmpWithBorder = Bitmap.createBitmap(bmp.getWidth() + borderSize * 2, bmp.getHeight() + borderSize * 2, bmp.getConfig());
Canvas canvas = new Canvas(bmpWithBorder);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bmp, borderSize, borderSize, null);
return bmpWithBorder;
}
remove those 2 lines from onItemClick
picker.setAdapter(adapter);
picker.scrollToPosition(position);
every time you setAdapter it resets position, and now you don't need to set a new position again.
this should work. If it doesn't, check this answer of mine (and their comments) about providing ID How to remain at a scroll position in RecyclerView after adding items at its first index and call notifydatasetchange
anybody can tell me how it is possible to sync the row elements size with the row position : getting smaller when above or below center of screen to fit to round screen on android wear watch ?
If you're working with wearables, you can use the WearableListView.
If so, in your adapter, you have your Item class which represents your row. This class can implement WearableListView.OnCenterProximityListener. There, you can animate your view in the callbacks : a small example without animation :
private static class Item extends LinearLayout implements WearableListView.OnCenterProximityListener {
private FontTextView label;
public Item(Context context) {
super(context);
View.inflate(context, R.layout.match_center_item, this);
label = (FontTextView) findViewById(R.id.label);
}
#Override
public void onCenterPosition(boolean animate) {
label.setAlpha(EXPAND_LABEL_ALPHA);
}
#Override
public void onNonCenterPosition(boolean animate) {
label.setAlpha(SHRINK_LABEL_ALPHA);
}
}
you can add RecyclerView.OnScrollListener to your RecyclerView and in on Scrolled you can iterate over all of child and find they position on screen something like (kotlin code):
for(i in 0..this.childCount){
val view = this.getChildAt(i)
when(i){
in 0..this.childCount/2 -1-> {view.scaleX = i.toFloat()/(this.childCount/2)
view.scaleY== i.toFloat()/(this.childCount/2) }
this.childCount/2 -> {view.scaleX = 1F ; view.scaleY = 1F}
else ->{view.scaleX = (this.childCount-i).toFloat()/(this.childCount/2)
view.scaleY== (this.childCount-i).toFloat()/(this.childCount/2) }
}
}
I want to create below layout under which item will scroll vertically. whenever we scroll up or down the centered(28) textview should be zoom in position.i found this link Gallery like view with center image zoom but they scroll horizontally using image based on their requirement. i need vertically scroll only using textview
please let me know if any body know the logic.......
First of all, thanks to #sanjay kumar had a good question.
After 2 days, I found a best solution for this question.
You can use RecyclerView and make sure the LinearLayoutManager should like this
LinearLayoutManager yourLinearLayout= new LinearLayoutManager(getApplicationContext());
yourLinearLayout.setOrientation(LinearLayoutManager.VERTICAL);
yourLinearLayout.setLayoutManager(yearLayoutManager);
The most difficult problem is how can you get the middle Item of recyclerView, well I think you can learn more in this link:
https://github.com/plattysoft/SnappingList
Finally, to make the middle Item bigger than the others. In the RecyclerView's Adapter should be like this
public class DateAdapter extends RecyclerView.Adapter<DateAdapter.DateViewHolder> {
private ArrayList<LabelerDate> dateDataList;
private static final int VIEW_TYPE_PADDING = 1;
private static final int VIEW_TYPE_ITEM = 2;
private int selectedItem = -1;
public DateAdapter(ArrayList<Datasource> data, int paddingWidthDate) {
this.dateDataList = data;
this.paddingWidthDate = paddingWidthDate;
}
#Override
public DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//todo
}
#Override
public void onBindViewHolder(DateViewHolder holder, int position) {
Datasource data = dateDataList.get(position);
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
holder.tvDate.setText(String.valueOf(data.valueDate));
holder.tvDate.setVisibility(View.VISIBLE);
holder.imgSmall.setVisibility(View.VISIBLE);
if (position == selectedItem) {
holder.tvDate.setTextColor(Color.parseColor("#094673"));
holder.tvDate.setTextSize(35);
holder.imgSmall.setBackgroundResource(R.color.textviewbold);
} else {
holder.tvDate.setTextColor(Color.GRAY);
holder.tvDate.setTextSize(35);
holder.imgSmall.setBackgroundResource(R.color.gray);
}
}
}
public void setSelecteditem(int selecteditem) {
this.selectedItem = selecteditem;
notifyDataSetChanged();
}
#Override
public int getItemViewType(int position) {
//todo
}
public class DateViewHolder extends RecyclerView.ViewHolder {
//todo
}}
After created The recyclerView, adapter and datasource. You can look at the onBindViewHolder(). If Item is selected then it's become bigger and change to black. If is not selected it will become Gray. My layout look like yours
use this library
Polidea view library
Put your TextView into a LinearLayout that lives on a ZoomView.
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.polidea.ZoomView
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:id="#+id/myLinearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</com.polidea.ZoomView>
</ScrollView>
The Polidea custom View didn't work in my case, as is. Had to a add a constructor with AttributeSet parameter, because it threw an xml inflation error. Also I had to make the ScrollView wrap only the TextView.
I have code like this
public static class MyViewHolder extends RecyclerView.ViewHolder {
#InjectView(R.id.text)
TextView label;
public MyViewHolder(View itemView) {
super(itemView);
ButterKnife.inject(this, itemView);
}
public void hide(boolean hide) {
label.setVisibility(hide ? View.GONE : View.VISIBLE);
}
}
which maps to a single row in a RecyclerView. R.id.text is in fact the root view of the layout that gets inflated and passed in to the constructor here.
I'm using the default implementation of LinearLayoutManager.
In bindViewHolder, I call hide(true) on an instance of MyViewHolder, but instead of collapsing the row as expected, the row becomes invisible, maintaining its height and position in the RecyclerView. Has anyone else run into this issue?
How do you hide items in a RecyclerView?
There is no built in way to hide a child in RV but of course if its height becomes 0, it won't be visible :). I assume your root layout does have some min height (or exact height) that makes it still take space even though it is GONE.
Also, if you want to remove a view, remove it from the adapter, don't hide it. Is there a reason why you want to hide instead of remove ?
Put method setVisibility(boolean isVisible) in ViewHolder.
You can change itemView params(width and height) for LayoutManager:
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
...
public void setVisibility(boolean isVisible){
RecyclerView.LayoutParams param = (RecyclerView.LayoutParams)itemView.getLayoutParams();
if (isVisible){
param.height = LinearLayout.LayoutParams.WRAP_CONTENT;
param.width = LinearLayout.LayoutParams.MATCH_PARENT;
itemView.setVisibility(View.VISIBLE);
}else{
itemView.setVisibility(View.GONE);
param.height = 0;
param.width = 0;
}
itemView.setLayoutParams(param);
}
public ViewHolder(View itemView) {
super(itemView);
...
}
}
and change visibility for ItemDecoration (Divider):
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
...
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
...
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getChildAt(i).getVisibility() == View.GONE)
continue;
/* draw dividers */
}
}
}
You CAN do it!
First, you need to detect which position of item that you want to hide. You can custom getItemViewType to do it.
Next, on onCreateViewHolder, depend on the view type. You can do something like this:
if(viewType == TYPE_HIDE) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_item, parent, false);
vHolder = new ViewHolder(context, v, viewType, this);
break;
}
return vHolder;
-> empty item is a layout that have nothing, (in other word, it is default layout whenever created). or code:
<?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="match_parent"
android:orientation="vertical">
</LinearLayout>
Hope it help!
Okay, so the way I did it in the end was I had my whole dataset, say, myObjects and I had scenarios where I would only want to show subsets of that dataset.
Since setting visibility of rows in RecyclerView doesn't cause the heights to collapse, and setting the heights of the rows did not appear to do anything either, what I had to do was just keep a secondary dataset called myObjectsShown which was nothing more than a List<Integer> that would index into myObjects to determine which objects would be displayed.
I would then intermittently update myObjectsShown to contain the correct indices.
Therefore,
public int getItemCount() {
return myObjectsShown.size();
}
and
public void onBindViewHolder(MyViewHolder holder, int position) {
Object myObject = myObjects.get(myObjectsShown.get(position));
// bind object to viewholder here...
}
For hiding view in RecyclerView I hide/show view in OnBindViewHolder:
if (item.isShown) {
vh.FooterLayout.setVisibility(View.Visible);
} else {
vh.FooterLayout.setVisibility(View.Gone);
}
And for example - from activity I simply redraw needed item:
_postListAdapter.notifyItemChanged(position)// if you want show/hide footer - position is amountOfPosts.size() and also change bool variable - amountOfPosts[amountOfPosts.size()].isShown
For the sake of completeness, you should note that setting view visibility to GONE would not hide the margins. You need to do something like this :
if(itemView.getVisibility() != GONE) itemView.setVisibility(GONE);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) itemView.getLayoutParams();
layoutParams.setMargins(0, 0, 0, 0);
itemView.setLayoutParams(layoutParams);