I am using below listener code
.listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
GifDrawable gifDrawable = null;
Handler handler = new Handler();
if (resource instanceof GifDrawable) {
gifDrawable = (GifDrawable) resource;
int duration = 0;
GifDecoder decoder = gifDrawable.getDecoder();
for (int i = 0; i < gifDrawable.getFrameCount(); i++) {
duration += decoder.getDelay(i);
}
handler.postDelayed(new Runnable() {
#Override
public void run() {
Intent intent = new Intent(SplashScreenActivity.this, MainActivity.class);
startActivity(intent);
SplashScreenActivity.this.finish();
}
}, (duration + 3000));
}
return false;
}
})
here I am not able to access getDecoder() at
GifDecoder decoder = gifDrawable.getDecoder();
How can I check if animation is completed. why getDecoder is not accessible here
I am using below glide dependency
implementation 'com.github.bumptech.glide:glide:4.7.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
Thanks in Advance
Glide.with(context) // replace with 'this' if it's in activity
.load(url)
.into(new DrawableImageViewTarget(memeBackground) {
public void onResourceReady(Drawable resource, #Nullable Transition<? super Drawable> transition) {
if (resource instanceof GifDrawable) {
((GifDrawable) resource).registerAnimationCallback(new Animatable2Compat.AnimationCallback() {
#Override
public void onAnimationEnd(Drawable drawable) {
super.onAnimationEnd(drawable);
log.D("animation is completed");
}
});
super.onResourceReady(resource, transition);
}}});
Related
developing an image picker using recyclerview with all images in devices for an android app, facing an issue when user scroll through images its memory usage keeps increasing eventually if lots of images there is oom exception, have searched for this issue over the internet, have applied several techniques but it is of little use, i have seen other app(whatsapp, olx) its picker scrolling is nice and no oom, i also tried dump heap in android studio to detect memory leakage, i found that there are as many viewholders as number of images, its not being recycled, am not able to find the reason, please help me in detecting the issue, attaching code..
/** adapter class for grid recyclerview*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<CreateList> galleryList;
private Context context;
private List<String> imageUrls = new ArrayList<>(3);
private List<Integer> positionList = new ArrayList<>(3);
int count = 0;
RequestOptions options;
RequestBuilder glide;
RequestManager glideMain;
int imgWidth;
public MyAdapter(Context context, ArrayList<CreateList> galleryList, RequestOptions options, RequestBuilder<Bitmap> glide, RequestManager glideMain, int imgWidth) {
this.galleryList = galleryList;
this.context = context;
this.options = options;
this.glide = glide;
this.glideMain = glideMain;
this.imgWidth = imgWidth;
}
#Override
public void onViewRecycled(#NonNull ViewHolder holder) {
holder.img.setOnClickListener(null);
holder.img.setColorFilter(null);
holder.img.setImageDrawable(null);
if(holder.title.getText().toString().equals("1")){
}
//holder.itemView.setOnClickListener(null);
glideMain.clear(holder.img);
super.onViewRecycled(holder);
}
#Override
public void onViewDetachedFromWindow(#NonNull ViewHolder holder) {
/* holder.img.setOnClickListener(null);
holder.img.setColorFilter(null);*/
holder.img.setImageDrawable(null);
// holder.itemView.setOnClickListener(null);
glideMain.clear(holder.img);
super.onViewDetachedFromWindow(holder);
}
#Override
public void onViewAttachedToWindow(#NonNull final ViewHolder holder) {
/* holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
holder.handleThumbnail(view);
}
});*/
super.onViewAttachedToWindow(holder);
}
public ArrayList<CreateList> getGalleryList() {
return galleryList;
}
public void setGalleryList(ArrayList<CreateList> galleryList) {
this.galleryList = galleryList;
}
public List<Integer> getPositionList() {
return positionList;
}
public void setPositionList(List<Integer> positionList) {
this.positionList = positionList;
}
public List<String> getImageUrls() {
return imageUrls;
}
public void setImageUrls(List<String> imageUrls) {
this.imageUrls = imageUrls;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.cell_layout, viewGroup, false);
//img.setOnClickListener();
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final MyAdapter.ViewHolder viewHolder, int pos) {
glide
.load(galleryList.get(pos).getImage_Location())
.thumbnail(0.1F)
.apply(options)
.into(viewHolder.img);
if (positionList.contains(pos)){
// view not selected
//viewHolder.parent.setBackgroundColor(Color.LTGRAY);
viewHolder.title.setVisibility(View.VISIBLE);
viewHolder.title.setText(String.valueOf(positionList.indexOf(pos)+1));
viewHolder.img.setColorFilter(viewHolder.getColorWithAlpha(Color.GREEN, 0.3f));
}
}
#Override
public int getItemCount() {
return galleryList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
private TextView title;
private ImageView img;
public ViewHolder(View view) {
super(view);
title = (TextView)view.findViewById(R.id.highlightText);
img = (ImageView) view.findViewById(R.id.img);
/* view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
handleThumbnail(v);
}
});*/
/* img = view.findViewById(R.id.img);*/
img.setLayoutParams(new ConstraintLayout.LayoutParams(imgWidth, imgWidth));
/*img.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// This is OnClick of any list Item
handleThumbnail(v);
}
});*/
/* img.setOnTouchListener(new View.OnTouchListener() {
private Rect rect;
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
img.setColorFilter(Color.argb(50, 0, 0, 0));
rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
}
if(event.getAction() == MotionEvent.ACTION_UP){
img.setColorFilter(Color.argb(0, 0, 0, 0));
}
if(event.getAction() == MotionEvent.ACTION_MOVE){
if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
img.setColorFilter(Color.argb(0, 0, 0, 0));
}
}
return false;
}
});*/
}
public void handleThumbnail(View v) {
if(!imageUrls.contains(galleryList.get(getAdapterPosition()).getImage_Location())) {
if(count<3) {
positionList.add(getAdapterPosition());
String selectedImgUrl = galleryList.get(getAdapterPosition()).getImage_Location();
imageUrls.add(selectedImgUrl);
//img.setBackground(context.getResources().getDrawable(R.drawable.button_background_checked));
img.setColorFilter(getColorWithAlpha(Color.GREEN, 0.3f));
//title.setVisibility(View.VISIBLE);
//title.setText(String.valueOf(count+1));
//View viewSelected = gridRecycler.findViewHolderForAdapterPosition(position).itemView;
title.setVisibility(View.VISIBLE);
title.setText(String.valueOf(count+1));
count++;
} else {
Toast.makeText().show();
}
} else {
imageUrls.remove(galleryList.get(getAdapterPosition()).getImage_Location());
positionList.remove(new Integer(getAdapterPosition()));
//imageUrls.remove( Integer.parseInt(title.getText().toString()));
title.setVisibility(View.GONE);
count--;
// img.setBackground(context.getResources().getDrawable(R.drawable.button_background));
img.setColorFilter(Color.argb(0, 0, 0, 0));
EventBus.getDefault().post(new MessageEvent(imageUrls, positionList));
}
}
public int getColorWithAlpha(int color, float ratio) {
int newColor = 0;
int alpha = Math.round(Color.alpha(color) * ratio);
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
newColor = Color.argb(alpha, r, g, b);
return newColor;
}
}
activity code using background thread to load data and set in adapter:
private class PrepareData extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... voids) {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int imgWidth = (int) (displayMetrics.widthPixels*0.32);
RequestOptions options = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.override(imgWidth/3)
.dontAnimate()
.centerCrop()
.skipMemoryCache(true);
final RequestManager glide = Glide.with(gridRecycler.getContext());
RequestBuilder builder = glide.asBitmap();
gridRecycler.setHasFixedSize(true);
final RecyclerView.LayoutManager layoutManager = new GridLayoutManager(gridRecycler.getContext(), 3);
createLists = getAllFolderImages(this);
adapter = new MyAdapter(this, createLists, options, builder, glide, imgWidth);
gridRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
glide.resumeRequests();
}
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL || newState==AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
glide.pauseRequests();
System.gc();
}
}
});
runOnUiThread(new Runnable() {
#Override
public void run() {
gridRecycler.setLayoutManager(layoutManager);
gridRecycler.setAdapter(adapter);
//put the code here that is giving exception
}
});
return null;
}
}
android profiling screen-shot, its visible that viewholder is consuming lots of memory and has lots of instances.
I am using Glide loader to load my images. My code is working but it changes images continuously. Here is my code:
Glide.with(ctx).load(image).asBitmap()
.dontTransform()
.placeholder(R.drawable.cart)
.into(new SimpleTarget < Bitmap > () {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
photoImageView.setImageBitmap(resource);
Glide.with(ctx).load(image).into(post_image);
post_image.setImageBitmap(resource);
}
Hey You can use glide utill class for better understanding
public class GlideUtil {
public static void loadImage(String url, ImageView imageView) {
Context context = imageView.getContext();
if(context!=null || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)) {
assert context != null;
ColorDrawable cd = new ColorDrawable(ContextCompat.getColor(context, R.color.blue_grey_500));
Glide.with(context)
.load(url)
.placeholder(cd)
.crossFade()
.centerCrop()
.into(imageView);
}
}
public static void loadProfileIcon(String url, ImageView imageView) {
Context context = imageView.getContext();
if(context!=null || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)) {
Glide.with(context)
.load(url)
.placeholder(R.drawable.ic_person_outline_black)
.dontAnimate()
.fitCenter()
.into(imageView);
}
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void loadImage(String url, ImageView mPhotoView, final ProgressBar mProgress) {
Context context = mPhotoView.getContext();
if(context!=null || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)) {
assert context != null;
ColorDrawable cd = new ColorDrawable(ContextCompat.getColor(context, R.color.blue_grey_500));
mProgress.setEnabled(true);
Glide.with(context)
.load(url)
.listener(new RequestListener<String, GlideDrawable>() {
#Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
mProgress.setVisibility(View.GONE);
return false;
}
#Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
mProgress.setVisibility(View.GONE);
return false;
}
})
.placeholder(cd)
.crossFade()
.centerCrop()
.into(mPhotoView);
}
}
}
Image changing occurs because of lazy loading of images in the glide. When the glide is downloading the new image previous image will be already loaded since you are reusing the view. To prevent this,.Before loading images clear the previous image before loading image.
Glide.clear(post_image);
EDIT:
Also before fetching the image from glide clear the old image of the imageView.
post_image.setImageDrawable(null);
Then load the new image from glide
Glide.with(ctx)
.load(image)
.asBitmap()
.dontTransform()
.placeholder(R.drawable.cart)
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
post_image.setImageBitmap(resource);
}
}
I have this code
Glide
.with(this)
.load(mUrl)
.asBitmap()
.thumbnail(Glide
.with(this)
.load(mPreviewUrl)
.asBitmap()
.animate(R.anim.fade_in))
.listener(new RequestListener<String, Bitmap>() {
#Override
public boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) {
mProgressBar.setVisibility(View.GONE);
return false;
}
#Override
public boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {
mProgressBar.setVisibility(View.GONE);
return false;
}
})
.into(new SimpleTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
mWallpaperImageView.setImageBitmap(resource);
createPaletteAsync(resource);
}
});
This downloads an image and returns a Bitmap object, which then I use to generate a palette of colour with the Palette library. I also want to load the bitmap in an ImageView, and I do that with mWallpaperImageView.setImageBitmap(resource) which loads the image without any fading or animation to smooth out the loading.
If I use glide like this:
Glide.with(this)
.load(mUrl)
.crossFade(500)
.into(mImageView)
then the image appears with fading but then I don't have a Bitmap object.
According to this answer,
Unfortunately, there isn't a built in way to cross fade Bitmaps. You
can however, use a custom BitmapImageViewTarget, and use a
TransitionDrawable in onResourceReady() to cross fade.
Fortunately, there's a trick to enable fading effect with bitmap in Glide.
For this you will need two classes,
FadingDrawable
final public class FadingDrawable extends BitmapDrawable {
// Only accessed from main thread.
private static final float FADE_DURATION = 200; //ms
private final float density;
Drawable placeholder;
long startTimeMillis;
boolean animating;
int alpha = 0xFF;
FadingDrawable(Context context, Bitmap bitmap, Drawable placeholder) {
super(context.getResources(), bitmap);
this.density = context.getResources().getDisplayMetrics().density;
this.placeholder = placeholder;
animating = true;
startTimeMillis = SystemClock.uptimeMillis();
}
/**
* Create or update the drawable on the target {#link android.widget.ImageView} to display the supplied bitmap
* image.
*/
static public void setBitmap(ImageView target, Context context, Bitmap bitmap) {
if (bitmap != null && !bitmap.isRecycled()) {
Drawable placeholder = target.getDrawable();
if (placeholder instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
}
FadingDrawable drawable = new FadingDrawable(context, bitmap, placeholder);
//this will avoid OverDraw
//target.setBackgroundDrawable(null);
//target.setBackgroundColor(0);
target.setImageDrawable(drawable);
}
}
/**
* Create or update the drawable on the target {#link android.widget.ImageView} to display the supplied
* placeholder image.
*/
static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {
target.setImageDrawable(placeholderDrawable);
if (target.getDrawable() instanceof AnimationDrawable) {
((AnimationDrawable) target.getDrawable()).start();
}
}
#Override
public void draw(Canvas canvas) {
if (!animating) {
super.draw(canvas);
} else {
float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;
if (normalized >= 1f) {
animating = false;
placeholder = null;
super.draw(canvas);
} else {
if (placeholder != null) {
placeholder.draw(canvas);
}
int partialAlpha = (int) (alpha * normalized);
super.setAlpha(partialAlpha);
super.draw(canvas);
super.setAlpha(alpha);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
invalidateSelf();
}
}
}
}
#Override
public void setAlpha(int alpha) {
this.alpha = alpha;
if (placeholder != null) {
placeholder.setAlpha(alpha);
}
super.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
if (placeholder != null) {
placeholder.setColorFilter(cf);
}
super.setColorFilter(cf);
}
#Override
protected void onBoundsChange(Rect bounds) {
if (placeholder != null) {
placeholder.setBounds(bounds);
}
super.onBoundsChange(bounds);
}
}
and GlideImageView
public class GlideImageView extends AppCompatImageView {
public GlideImageView(Context context) {
this(context, null);
}
public GlideImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GlideImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Drawable placeholder = getDrawable();
if (placeholder instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
Glide.clear(this);
}
}
#Override
public void setImageBitmap(Bitmap bitmap) {
if (bitmap != null) FadingDrawable.setBitmap(this, getContext(), bitmap);
}
public void setImageBitmapWithoutAnimation(Bitmap bitmap) {
super.setImageBitmap(bitmap);
}
}
Now change your mWallpaperImageView from ImageView to
<com.yourpackage.GlideImageView
android:id="#+id/mWallpaperImageView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And finally remove all external animation effect from Glide configuration,
Glide
.with(this)
.load(mUrl)
.asBitmap()
.thumbnail(Glide
.with(this)
.load(mPreviewUrl)
.asBitmap()
.listener(new RequestListener<String, Bitmap>() {
#Override
public boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) {
mProgressBar.setVisibility(View.GONE);
return false;
}
#Override
public boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {
mProgressBar.setVisibility(View.GONE);
return false;
}
})
.into(new SimpleTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
mWallpaperImageView.setImageBitmap(resource);
createPaletteAsync(resource);
}
});
Tested and working properly!
You can use the value of the alpha of an image to create a fade in or fade out effect:
In Java
yourimage.animate().setAlpha(0f).setDuration(2000);
given that the image's alpha starts with alpha =1 (not transparent).
so the image will disappear (transparent) during 2 seconds
I'm switching from Picasso to Glide. Everything works fine except I cannot find a method to get an error callback. I want to retrieve a Bitmap, pass it on and generate an Android Palette from it. Also, while an errorDrawable can be provided to a load call, it won't show up in onResourceReady when using a SimpleTarget.
In Picasso I did it like this:
target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
//handle Bitmap, generate Palette etc.
}
#Override
public void onBitmapFailed(final Drawable errorDrawable) {
// use errorDrawable to generate Palette
}
#Override
public void onPrepareLoad(final Drawable placeHolderDrawable) {
}
};
int width = (int) DisplayUnitsConverter.dpToPx(this, 120);
int height = (int) DisplayUnitsConverter.dpToPx(this, 40);
Picasso.with(this).load(config.getPathToLogo()).resize(width, height).error(errorDrawableId).into(target);
My glide code looks like this:
Glide.with(context)
.load(config.getPathToLogo())
.asBitmap()
.into(new SimpleTarget<Bitmap>(width, height) {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
//handle Bitmap, generate Palette etc.
}
});
Thanks.
For everyone with the same problem - you need to use listener method. For example:
Glide.with(activity)
.load(getPhoto().getUrl())
.apply(
new RequestOptions()
.error(R.drawable.icon_placeholder)
.centerCrop()
)
.listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
//on load failed
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
//on load success
return false;
}
})
.transition(withCrossFade())
.into(view);
You are using SimpleTarget that implements the interface Target that defines the method onLoadFailed so you only need to do:
Glide.with(context)
.load(config.getPathToLogo())
.asBitmap()
.into(new SimpleTarget<Bitmap>(width, height) {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
//handle Bitmap, generate Palette etc.
}
#Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
// Do something.
}
});
I'm using Glide for setting png images (with transparencies) in ImageView in this mode:
Glide.with(context).load(url)
.crossFade()
.placeholder(R.drawable.no_contest)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into((ImageView)container);
Is it possible to set the backgroundcolor of the image without setting the background color of ImageView ?
thanks
LayerDrawable should be used:
.into(new SimpleTarget<GlideDrawable>() {
#Override
public void onResourceReady(GlideDrawable resource,
GlideAnimation<? super GlideDrawable> glideAnimation) {
final ShapeDrawable background = new ShapeDrawable();
background.getPaint().setColor("color here");
final Drawable[] layers = {background, resource};
container.setImageDrawable(new LayerDrawable(layers));
}
});
This will set background color according to your gif.
Glide code:
Glide.with(context).load(newspojo.getImageLink())
.asGif()
.listener(new RequestListener<String, GifDrawable>() {
#Override
public boolean onException(Exception e, String model, Target<GifDrawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(GifDrawable resource, String model, Target<GifDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
final Pojo pojo = new Pojo();
final GifDrawable resources = resource;
Palette.from(resource.getFirstFrame()).generate(new Palette.PaletteAsyncListener() {
#Override
public void onGenerated(#NonNull Palette palette) {
if (pojo.getPosterPalette() != null) {
setUpInfoBackgroundColor(holder.ivRow, palette);
} else {
Palette.from(resources.getFirstFrame()).generate(new Palette.PaletteAsyncListener() {
public void onGenerated(Palette palette) {
pojo.setPosterPalette(palette);
setUpInfoBackgroundColor(holder.ivRow, palette);
}
});
}
}
});
return false;
}
})
.into(holder.ivRow);
}
Here is Pojo:
public class Pojo {
public Palette posterPalette;
public Palette getPosterPalette() {
return posterPalette;
}
public void setPosterPalette(Palette posterPalette) {
this.posterPalette = posterPalette;
}
public Pojo(){
}
}
and add in build.gradle:
implementation 'com.android.support:palette-v7:27.1.1'