RecyclerView uses wrong values - android

So, I use a RecyclerView for displaying a grid. In the onBindViewHolder method of its adapter I update the view content. I put a part of the update into another thread to keep the UI alive.
#Override
public void onBindViewHolder (final MovieHolder holder, final int position) {
// Blah, blah. Set movie details. Everything is fine.
// Generate a palette or load from the memory and adjust the background color of the view.
new Thread(new Runnable() {
#Override
public void run () {
Palette palette = getPalette();
final Palette.Swatch bestSwatch = findBestSwatch(palette)
if (bestSwatch != null) {
activity.runOnUiThread(new Runnable(
#Override
public void run() {
holder.layout.setBackgroundColor(bestSwatch.getRgb());
}
));
}
}
}).start();
}
It works well, except when no bestSwatch was found. Then for the first time, it displays a correct background color and when I scroll down and back it will have a random color from one of the views.
Why does RecyclerView changes the color of my view when I don't update it?

RecyclerView reuses its rows, the ViewHolders, hence its name. So your problem is that a recycled view has the background color it was set to at another recent position.
Solution to this is simple...
Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
#Override
public void onGenerated(Palette palette) {
Palette.Swatch swatch = palette.getDarkVibrantSwatch();
if (swatch == null)
swatch = new Palette.Swatch(ContextCompat.getColor(context, R.color.fallback_background), 4);
holder.layout.setBackgroundColor(swatch.getRgb());
}
});
So, you always set a BackgroundColor. When there's no swatch generated, you create one using a fallback layout background color you define at colors.xml

Related

Drag-n-Move - How to avoid RecyclerView background fill the entire screen even there's only a few items in RecyclerView

Currently, I'm implementing drag-n-move feature according to https://medium.com/#ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd and code example from https://github.com/iPaulPro/Android-ItemTouchHelper-Demo/
Here's the outcome
I would like to have background of RecycleView to be visible, when the item is being moved.
Here's changes I had did
Set RecycleView background color to red - recyclerView.setBackgroundColor(Color.RED);
Provide a solid white color, on the item.
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="#dimen/activity_horizontal_margin"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ImageView
android:id="#+id/handle"
android:layout_width="?listPreferredItemHeight"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|right"
android:scaleType="center"
android:src="#drawable/ic_reorder_grey_500_24dp" />
Here's my desired outcome.
However, there's 1 shortcoming. When, there's only a few items in RecyclerView. Those empty area will also be filled with background color. Please see the below screenshot. The below RecyclerView only contain 3 items.
I had try to make RecyclerView's height wrap_content
recyclerView.setLayoutParams(
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
);
However, it makes no difference.
If the RecyclerView is longer than the area allocated to it on the screen then there is no issue: Just set the background color of the RecyclerView to red. However, if the items in the RecyclerView do not fill up the space allocated to the RecyclerView in the layout then you will see the red background in the empty space. This is what you want to eliminate.
To do this, set a OnGlobalLayoutListener on the RecyclerView and check if there is excess space or not. If there is not excess space, then just set the background color to red; otherwise, create a BitmapDrawable filled with red and properly sized to provide a background to just the items on the screen and not large enough to spill into the excess area.
Here is the code that accomplishes this in RecyclerListFragment of the project you mention.
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final RecyclerView recyclerView = new RecyclerView(container.getContext());
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (recyclerView.getChildCount() == 0) {
return;
}
int lastChildBottom = recyclerView.getChildAt(recyclerView.getChildCount() - 1).getBottom();
if (lastChildBottom >= recyclerView.getHeight()) {
recyclerView.setBackgroundColor(Color.RED);
return;
}
Bitmap bitmap = Bitmap.createBitmap(recyclerView.getWidth(), lastChildBottom, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.RED);
BitmapDrawable d = new BitmapDrawable(getResources(), bitmap);
d.setGravity(Gravity.TOP);
recyclerView.setBackground(d);
recyclerView.invalidate();
}
});
return recyclerView;
}
Here is a video of the effect:
If you have a swipe-to-remove gesture implemented, you will also have to invoke the listener when an item is removed. I also had to set the background color to a non-zero value in onItemClear() of RecyclerListAdapter.
An easier way is to define a drawable that can be set as a background to a RecyclerView that draws only behind the item views.
public class RecyclerViewBackground extends Drawable {
private RecyclerView mRecyclerView;
private Bitmap mBitmap;
private Paint mPaint;
RecyclerViewBackground() {
super();
mPaint = new Paint();
mPaint.setColor(Color.RED);
}
#Override
public void draw(Canvas canvas) {
if (mRecyclerView.getChildCount() == 0) {
return;
}
int bottom = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1).getBottom();
if (bottom >= mRecyclerView.getHeight()) {
bottom = mRecyclerView.getHeight();
}
canvas.drawRect(0, 0, canvas.getWidth(), bottom, mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
public void attachRecyclerView(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
}
}
Attach this Drawable to the RecyclerView as follows:
RecyclerViewBackground bg = new RecyclerViewBackground();
bg.attachRecyclerView(recyclerView);
recyclerView.setBackground(bg);
This will also take care of swipe to delete.

Using Android asynchronous Palette generator for color transition animation

In my app I have a ViewPager that holds images. When the user scrolls through the images, there is a color animation that transitions the action bar color and status bar color between the two bitmap's palette's vibrant colors. The issue I see is that while it works great, it becomes very choppy when large images are selected.
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Palette paletteBefore = Palette.from(imagePagerAdapter.bitmap.get(position)).generate();
Palette paletteAfter = Palette.from(imagePagerAdapter.bitmap.get(position + 1)).generate();
//Using this to get the average color between the two palette colors at the current position offset.
int color = (Integer) new ArgbEvaluator().evaluate(positionOffset, paletteBefore.getVibrantColor(Color.parseColor("#2196F3")), paletteAfter.getVibrantColor(Color.parseColor("#2196F3")));
int darkColor = (Integer) new ArgbEvaluator().evaluate(positionOffset, paletteBefore.getDarkVibrantColor(Color.parseColor("#1976D2")), paletteAfter.getDarkVibrantColor(Color.parseColor("#1976D2")));
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().setStatusBarColor(darkColor);
}
}
I know that there is a built-in asynchronous method that generates the palette in a background thread, but I am unable to access the generated palette:
Palette.from(imagePagerAdapter.bitmap.get(position)).generate(new Palette.PaletteAsyncListener() {
#Override
public void onGenerated(Palette p) {
paletteBefore = p; //Variable "paletteBefore" is accessed from within class, needs to be declared final
}
});
Even if I create a global variable to hold a palette object, it always ends up being null. I have no idea how to solve this, any ideas?
As you said, this is generating them asynchronously.
You cannot guarantee that both palettes will be created at the same time just in time to generate your colors.
The time taken will depend on the size of the image.
I would move all palette generation out of onPageScrolled()
Try creating the palettes as soon as the bitmap is accessible from each of your fragment/view
That way there might be a better chance of having them by the time the user scrolls.

Strange behaviour in customView with onClick event

I am using this class to draw TriangleShapeView over ImageView that changes its color and drawable image upon user click event.
in RecyclerView onBindViewHolder method, i check against
feedModel.isSubscribed() then set TriangleShapeView color and drawable image accordingly:
public void onBindViewHolder(FeedViewHolder holder, final int position) {
final FeedModel feedModel =this.feedCollection.get(position);
if (feedModel.isSubscribed()) {
holder.mTrView.setBackgroundColor(Color.RED);
holder.mTrView.setDrawable(holder.mTrView.getContext().getResources().getDrawable(R.drawable.ic_check));
} else {
holder.mTrView.setBackgroundColor(Color.BLACK);
holder.mTrView.setDrawable(holder.mTrView.getContext().getResources().getDrawable(R.drawable.ic_plus));
}
in setOnClickListener:
holder.itemView.setOnClickListener(v -> {
if (FeedAdapter.this.onItemClieckListener != null){
FeedAdapter.this.onItemClieckListener.onFeedItemClicked(feedModel);
if (feedModel.isSubscribed()) {
feedModel.setIsSubscribed(false);
notifyItemChanged(position);
} else {
feedModel.setIsSubscribed(true);
notifyItemChanged(position);
}
}
});
this works fine when items get loaded for first time but when user clickes:
- 1st & 2nd time: drawable image got changes as desired but the color
remains same.
- 3rd time both drawable image and color gets changes
i am using the following xml layout to inflate this custom view:
<cardView
<RelativeLayout
....
<com.xxx.TriangleShapeView
android:id="#+id/trView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:elevation="15dp"
TriangleShapeView:imgPadding="5dp"
TriangleShapeView:triangleBackground="#color/cardview_dark_background"
TriangleShapeView:img="#drawable/ic_plus"/>
i believe, the FeedModel gets updated once OnClick is called and checks are fine in onBindViewHoldr method. so i think the problem is in the class mentioned in the above link
What i am looking to achieve is:
if feedModel.isSubscribed then change the color to red and drawable to check-sign icon. else, keep the initial values as in the layout xml.
also react upon onClick and change the color and image
I think you're right and the error in this method.
public void setBackgroundColor(int backgroundColor) {
this.backgroundColor = backgroundColor;
invalidate();
}
the paint color get assigned in the constructor and onDraw method.
try to add to this method before invalidate() the line with paint.setColor(backgroundColor);

Set a gradient background

In my Activity I need to change an ImageView background using a gradient, so I use an image with a transparent area, changing its background when I need. Here's some code:
private static View myImage;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myActivityLayout);
myImage = findViewById(R.id.myImageID);
}
[...]
private void myImageUpdate() {
GradientDrawable gradient;
int[] colors = {0xFF00FF00, 0xFF0000FF};
// I make some changes to these colors..
gradient = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, colors);
myImage.setBackgroundDrawable(gradient);
}
Now, the problem is:
If I call myImageUpdate() within onCreate() method, everything works fine.
If I call myImageUpdate() from another part of the code (like an onClick callback), I can't set my backgroud!
* UPDATE *
Guys, this code is fine... I was calling my method in a wrong (not directly reachable) line! My apologies...
I don't think this will fix it since you said myImageUpdate gets called within onClick... but try this..
runOnUiThread(new Runnable() {
public void run() {
myImage.setBackgroundDrawable(gradient);
}
});
you might have to make gradient variable final..
final GradientDrawable gradient = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, colors);
Try myImage.invalidate(). This will force the system to redraw the view.

Adding items into listview dynamically [duplicate]

In iOS, there is a very easy and powerful facility to animate the addition and removal of UITableView rows, here's a clip from a youtube video showing the default animation. Note how the surrounding rows collapse onto the deleted row. This animation helps users keep track of what changed in a list and where in the list they were looking at when the data changed.
Since I've been developing on Android I've found no equivalent facility to animate individual rows in a TableView. Calling notifyDataSetChanged() on my Adapter causes the ListView to immediately update its content with new information. I'd like to show a simple animation of a new row pushing in or sliding out when the data changes, but I can't find any documented way to do this. It looks like LayoutAnimationController might hold a key to getting this to work, but when I set a LayoutAnimationController on my ListView (similar to ApiDemo's LayoutAnimation2) and remove elements from my adapter after the list has displayed, the elements disappear immediately instead of getting animated out.
I've also tried things like the following to animate an individual item when it is removed:
#Override
protected void onListItemClick(ListView l, View v, final int position, long id) {
Animation animation = new ScaleAnimation(1, 1, 1, 0);
animation.setDuration(100);
getListView().getChildAt(position).startAnimation(animation);
l.postDelayed(new Runnable() {
public void run() {
mStringList.remove(position);
mAdapter.notifyDataSetChanged();
}
}, 100);
}
However, the rows surrounding the animated row don't move position until they jump to their new positions when notifyDataSetChanged() is called. It appears ListView doesn't update its layout once its elements have been placed.
While writing my own implementation/fork of ListView has crossed my mind, this seems like something that shouldn't be so difficult.
Thanks!
Animation anim = AnimationUtils.loadAnimation(
GoTransitApp.this, android.R.anim.slide_out_right
);
anim.setDuration(500);
listView.getChildAt(index).startAnimation(anim );
new Handler().postDelayed(new Runnable() {
public void run() {
FavouritesManager.getInstance().remove(
FavouritesManager.getInstance().getTripManagerAtIndex(index)
);
populateList();
adapter.notifyDataSetChanged();
}
}, anim.getDuration());
for top-to-down animation use :
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="20%p" android:toYDelta="-20"
android:duration="#android:integer/config_mediumAnimTime"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="#android:integer/config_mediumAnimTime" />
</set>
The RecyclerView takes care of adding, removing, and re-ordering animations!
This simple AndroidStudio project features a RecyclerView. take a look at the commits:
commit of the classic Hello World Android app
commit, adding a RecyclerView to the project (content not dynamic)
commit, adding functionality to modify content of RecyclerView at runtime (but no animations)
and finally...commit adding animations to the RecyclerView
Take a look at the Google solution. Here is a deletion method only.
ListViewRemovalAnimation project code and Video demonstration
It needs Android 4.1+ (API 16). But we have 2014 outside.
Since ListViews are highly optimized i think this is not possible to accieve. Have you tried to create your "ListView" by code (ie by inflating your rows from xml and appending them to a LinearLayout) and animate them?
Have you considered animating a sweep to the right? You could do something like drawing a progressively larger white bar across the top of the list item, then removing it from the list. The other cells would still jerk into place, but it'd better than nothing.
call
listView.scheduleLayoutAnimation();
before changing the list
I hacked together another way to do it without having to manipulate list view. Unfortunately, regular Android Animations seem to manipulate the contents of the row, but are ineffectual at actually shrinking the view. So, first consider this handler:
private Handler handler = new Handler() {
#Override
public void handleMessage(Message message) {
Bundle bundle = message.getData();
View view = listView.getChildAt(bundle.getInt("viewPosition") -
listView.getFirstVisiblePosition());
int heightToSet;
if(!bundle.containsKey("viewHeight")) {
Rect rect = new Rect();
view.getDrawingRect(rect);
heightToSet = rect.height() - 1;
} else {
heightToSet = bundle.getInt("viewHeight");
}
setViewHeight(view, heightToSet);
if(heightToSet == 1)
return;
Message nextMessage = obtainMessage();
bundle.putInt("viewHeight", (heightToSet - 5 > 0) ? heightToSet - 5 : 1);
nextMessage.setData(bundle);
sendMessage(nextMessage);
}
Add this collection to your List adapter:
private Collection<Integer> disabledViews = new ArrayList<Integer>();
and add
public boolean isEnabled(int position) {
return !disabledViews.contains(position);
}
Next, wherever it is that you want to hide a row, add this:
Message message = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putInt("viewPosition", listView.getPositionForView(view));
message.setData(bundle);
handler.sendMessage(message);
disabledViews.add(listView.getPositionForView(view));
That's it! You can change the speed of the animation by altering the number of pixels that it shrinks the height at once. Not real sophisticated, but it works!
After inserting new row to ListView, I just scroll the ListView to new position.
ListView.smoothScrollToPosition(position);
I haven't tried it but it looks like animateLayoutChanges should do what you're looking for. I see it in the ImageSwitcher class, I assume it's in the ViewSwitcher class as well?
Since Android is open source, you don't actually need to reimplement ListView's optimizations. You can grab ListView's code and try to find a way to hack in the animation, you can also open a feature request in android bug tracker (and if you decided to implement it, don't forget to contribute a patch).
FYI, the ListView source code is here.
Here's the source code to let you delete rows and reorder them.
A demo APK file is also available. Deleting rows is done more along the lines of Google's Gmail app that reveals a bottom view after swiping a top view. The bottom view can have an Undo button or whatever you want.
As i had explained my approach in my site i shared the link.Anyways the idea is create bitmaps
by getdrawingcache .have two bitmap and animate the lower bitmap to create the moving effect
Please see the following code:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View rowView, int positon, long id)
{
listView.setDrawingCacheEnabled(true);
//listView.buildDrawingCache(true);
bitmap = listView.getDrawingCache();
myBitmap1 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), rowView.getBottom());
myBitmap2 = Bitmap.createBitmap(bitmap, 0, rowView.getBottom(), bitmap.getWidth(), bitmap.getHeight() - myBitmap1.getHeight());
listView.setDrawingCacheEnabled(false);
imgView1.setBackgroundDrawable(new BitmapDrawable(getResources(), myBitmap1));
imgView2.setBackgroundDrawable(new BitmapDrawable(getResources(), myBitmap2));
imgView1.setVisibility(View.VISIBLE);
imgView2.setVisibility(View.VISIBLE);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(0, rowView.getBottom(), 0, 0);
imgView2.setLayoutParams(lp);
TranslateAnimation transanim = new TranslateAnimation(0, 0, 0, -rowView.getHeight());
transanim.setDuration(400);
transanim.setAnimationListener(new Animation.AnimationListener()
{
public void onAnimationStart(Animation animation)
{
}
public void onAnimationRepeat(Animation animation)
{
}
public void onAnimationEnd(Animation animation)
{
imgView1.setVisibility(View.GONE);
imgView2.setVisibility(View.GONE);
}
});
array.remove(positon);
adapter.notifyDataSetChanged();
imgView2.startAnimation(transanim);
}
});
For understanding with images see this
Thanks.
I have done something similar to this. One approach is to interpolate over the animation time the height of the view over time inside the rows onMeasure while issuing requestLayout() for the listView. Yes it may be be better to do inside the listView code directly but it was a quick solution (that looked good!)
Just sharing another approach:
First set the list view's android:animateLayoutChanges to true:
<ListView
android:id="#+id/items_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"/>
Then I use a handler to add items and update the listview with delay:
Handler mHandler = new Handler();
//delay in milliseconds
private int mInitialDelay = 1000;
private final int DELAY_OFFSET = 1000;
public void addItem(final Integer item) {
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
new Thread(new Runnable() {
#Override
public void run() {
mDataSet.add(item);
runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
}
}).start();
}
}, mInitialDelay);
mInitialDelay += DELAY_OFFSET;
}

Categories

Resources