I want to get a "full page" screenshot of the activity. The view contains a RecyclerView with many items.
I can take a screenshot of the current view with this function:
public Bitmap getScreenBitmap() {
View v= findViewById(R.id.container).getRootView();
v.setDrawingCacheEnabled(true);
v.buildDrawingCache(true);
Bitmap b = Bitmap.createBitmap(v.getDrawingCache());
v.setDrawingCacheEnabled(false); // clear drawing cache
return b;
}
But it contains only the items I can view normally (as expected).
Is there some way to make the RecyclerView magically show in full length (display all items at once) when I take the screenshot?
If not, how should I approach this problem?
Inspired from Yoav's answer. This code works for recyclerview item types and probably regardless of it's size.
It was tested with a recyclerview having linearlayout manager and three item types. Yet to check it with other layout managers.
public Bitmap getScreenshotFromRecyclerView(RecyclerView view) {
RecyclerView.Adapter adapter = view.getAdapter();
Bitmap bigBitmap = null;
if (adapter != null) {
int size = adapter.getItemCount();
int height = 0;
Paint paint = new Paint();
int iHeight = 0;
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> bitmaCache = new LruCache<>(cacheSize);
for (int i = 0; i < size; i++) {
RecyclerView.ViewHolder holder = adapter.createViewHolder(view, adapter.getItemViewType(i));
adapter.onBindViewHolder(holder, i);
holder.itemView.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight());
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
Bitmap drawingCache = holder.itemView.getDrawingCache();
if (drawingCache != null) {
bitmaCache.put(String.valueOf(i), drawingCache);
}
// holder.itemView.setDrawingCacheEnabled(false);
// holder.itemView.destroyDrawingCache();
height += holder.itemView.getMeasuredHeight();
}
bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888);
Canvas bigCanvas = new Canvas(bigBitmap);
bigCanvas.drawColor(Color.WHITE);
for (int i = 0; i < size; i++) {
Bitmap bitmap = bitmaCache.get(String.valueOf(i));
bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint);
iHeight += bitmap.getHeight();
bitmap.recycle();
}
}
return bigBitmap;
}
Here is my solution for LinearLayoutManager when all the items are on same size and there is only one type of item. This solution is based on This answer.
Note: It can possibly lead to out of memory error.
public static Bitmap getRecyclerViewScreenshot(RecyclerView view) {
int size = view.getAdapter().getItemCount();
RecyclerView.ViewHolder holder = view.getAdapter().createViewHolder(view, 0);
view.getAdapter().onBindViewHolder(holder, 0);
holder.itemView.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight());
Bitmap bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), holder.itemView.getMeasuredHeight() * size,
Bitmap.Config.ARGB_8888);
Canvas bigCanvas = new Canvas(bigBitmap);
bigCanvas.drawColor(Color.WHITE);
Paint paint = new Paint();
int iHeight = 0;
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
bigCanvas.drawBitmap(holder.itemView.getDrawingCache(), 0f, iHeight, paint);
holder.itemView.setDrawingCacheEnabled(false);
holder.itemView.destroyDrawingCache();
iHeight += holder.itemView.getMeasuredHeight();
for (int i = 1; i < size; i++) {
view.getAdapter().onBindViewHolder(holder, i);
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
bigCanvas.drawBitmap(holder.itemView.getDrawingCache(), 0f, iHeight, paint);
iHeight += holder.itemView.getMeasuredHeight();
holder.itemView.setDrawingCacheEnabled(false);
holder.itemView.destroyDrawingCache();
}
return bigBitmap;
}
Note 2: It has originally been written in Kotlin. Here is the original code used by me.
Take the Screenshot of complete Recyclerview regardless of its items and item types:
This piece of code works like a charm.
Here is a clean way to do this short and precise:
recyclerView.measure(
View.MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
Bitmap bm = Bitmap.createBitmap(recyclerView.getWidth(), recyclerView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
recyclerView.draw(new Canvas(bm));
saveImage(bm);
ImageView im
= new ImageView(getActivity());
im.setImageBitmap(bm);
new AlertDialog.Builder(getActivity()).setView(im).show();
The best solution I found is
create a new NestedScrollView
add it to recyclerview's parent
remove recyclerview from its parent "it's important to add it to a new parent"
add recyclerview to nestedscrollview
take screenshot of nestedscrollview
add recyclerview to its main parent.
nestedScreenShot = new NestedScrollView(getContext());
constraintLayout.removeView(recyclerView);
ConstraintLayout.LayoutParams params =new
Constraints.LayoutParams(viewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
nestedScreenShot.setLayoutParams(params);
Drawable scrollBackground = scrollView.getBackground();
if (scrollBackground != null) {
tempNestedScreenShot.setBackground(scrollBackground);
}
tempNestedScreenShot.setPadding(scrollView.getLeft(),
scrollView.getTop(), scrollView.getRight(), scrollView.getBottom());
constraintLayout.addView(nestedScreenShot);
nestedScreenShot.addView(recyclerView, params);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
nestedScreenShot.removeView(recyclerView);
constraintLayout.removeView(nestedScreenShot);
nestedScreenShot = null;
constraintLayout.addView(recyclerView, params);
}
}, 8000);
takescreenshotOfNested(nestedScreenShot);
This method will return screenshot of nestedscrollview
private Bitmap takescreenshotOfNested(View u) {
NestedScrollView viewNested = null;
ScrollView viewScroll = null;
if (u instanceof NestedScrollView) {
viewNested = (NestedScrollView) u;
} else if (u instanceof ScrollView) {
viewScroll = (ScrollView) u;
}
Bitmap bitmap;
if (viewNested != null) {
viewNested.setDrawingCacheEnabled(false);
viewNested.invalidate();
viewNested.getChildAt(0).setDrawingCacheEnabled(false);
viewNested.getChildAt(0).invalidate();
bitmap = Bitmap.createBitmap(viewNested.getChildAt(0).getWidth(),
viewNested.getChildAt(0).getHeight() + 8, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(activity.getResources().getColor(R.color.layout_background));
viewNested.draw(canvas);
} else {
try {
viewScroll.setDrawingCacheEnabled(false);
viewScroll.invalidate();
} catch (Exception ignored) {
}
viewScroll.getChildAt(0).setDrawingCacheEnabled(false);
viewScroll.getChildAt(0).invalidate();
bitmap = Bitmap.createBitmap(viewScroll.getChildAt(0).getWidth(),
viewScroll.getChildAt(0).getHeight() + 8, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(activity.getResources().getColor(R.color.layout_background));
viewScroll.draw(canvas);
}
return bitmap;
}
don't forget to recycle the bitmap after using it
I wrote a method to get a screenshot of a few different views:
private static Bitmap getBitmapFromView(View view) {
if (view.getVisibility() != View.VISIBLE) {
view = getNextView(view);
Log.d(TAG, "New view id: " + view.getId());
}
//Define a bitmap with the same size as the view
Bitmap returnedBitmap;
if (view instanceof ScrollView) {
returnedBitmap = Bitmap.createBitmap(((ViewGroup) view).getChildAt(0).getWidth(), ((ViewGroup) view).getChildAt(0).getHeight(), Bitmap.Config.ARGB_8888);
} else if (view instanceof RecyclerView) {
view.measure(
View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
} else {
returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
}
//Bind a canvas to it
Canvas canvas = new Canvas(returnedBitmap);
//Get the view's background
Drawable bgDrawable = view.getBackground();
if (bgDrawable != null) {
//has background drawable, then draw it on the canvas
bgDrawable.draw(canvas);
} else {
//does not have background drawable, then draw white background on the canvas
canvas.drawColor(Color.WHITE);
}
// draw the view on the canvas
view.draw(canvas);
//return the bitmap
return returnedBitmap;
}
/**
* If the base view is not visible, then it has no width or height.
* This causes a problem when we are creating a PDF based on its size.
* This method gets the next visible View.
*
* #param view The invisible view
* #return The next visible view after the given View, or the original view if there's no more
* visible views.
*/
private static View getNextView(View view) {
if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) {
ViewGroup group = (ViewGroup) view.getParent();
View child;
boolean getNext = false;
//Iterate through all views from parent
for (int i = 0; i < group.getChildCount(); i++) {
child = group.getChildAt(i);
if (getNext) {
//Make sure the view is visible, else iterate again until we find a visible view
if (child.getVisibility() == View.VISIBLE) {
Log.d(TAG, String.format(Locale.ENGLISH, "CHILD: %s : %s", child.getClass().getSimpleName(), child.getId()));
view = child;
}
}
//Iterate until we find out current view,
// then we want to get the NEXT view
if (child.getId() == view.getId()) {
getNext = true;
}
}
}
return view;
}
Related
I would like to change position of bitmap inside Canvas from Activity class. My solution from below only draws first value of x (left) as zero, later on does not change x value. But my Logcat shows that x value changes everytime when I call it from Activity. I would like to know where is my mistake ? onDraw method does not change position of bitmap.
Custome view:
public class TheCustomView extends View {
Bitmap scaledRollerBottom;
Bitmap backgroundMask;
Bitmap rollerBottom;
Bitmap overlayBitmap;
float x;
public TheCustomView(Context context) {
super(context);
}
public TheCustomView(Context context, AttributeSet attr) {
super(context, attr);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
backgroundMask = BitmapFactory.decodeResource(getResources(), R.drawable.background_new, options);
rollerBottom = BitmapFactory.decodeResource(getResources(), R.drawable.roller_bottom, options);
Bitmap scaledBackgroundMask = Bitmap.createScaledBitmap(backgroundMask, 1000, 1000, true);
scaledRollerBottom = Bitmap.createScaledBitmap(rollerBottom, 120, 31, false);
// int bitmap1Width = scaledBackgroundMask.getWidth();
// int bitmap1Height = scaledBackgroundMask.getHeight();
// overlayBitmap = Bitmap.createBitmap(bitmap1Width, bitmap1Height, scaledBackgroundMask.getConfig());
}
#Override
protected void onDraw(Canvas canvas) {
Log.v("X ", "is " + x);
canvas.drawBitmap(scaledRollerBottom, (float) x, (float) 915, new Paint());
super.onDraw(canvas);
}
}
Calling from activity class
if (callback2 > 0 && callback2 <= 0.1) {
customView.x = 468;
customView.draw(canvas);
// customView.invalidate();
} else if (callback2 < 0) {
customView.x = 650;
customView.draw(canvas);
// customView.invalidate();
} else if (callback2 > 0.1) {
customView.x = 270;
customView.draw(canvas);
//customView.invalidate();
}
When create Custom View Class and run, logcat console shows error.
The specified child already has a parent. You must call removeView()
on the child's parent first.at
android.view.ViewGroup.addViewInner(ViewGroup.java:4312)
How can fix I it?
Here is my code:
public class CanvasView extends View {
ArrayList<Item> alTemp;
ArrayList<Item> alViews;
Bitmap bitmap2;
Bitmap bitmap;
int frontBack;
Context mContext;
Templates mTemplates;
RelativeLayout root;
float scale;
public CanvasView(Context context, float scale, ArrayList<Item> arrayList, Templates templates, int frontBack, Bitmap bitmap, Bitmap bitmap2) {
super(context);
this.mContext = context;
this.scale = scale;
this.alTemp = arrayList;
this.mTemplates = templates;
this.frontBack = frontBack;
this.bitmap = bitmap;
this.bitmap2 = bitmap2;
setData();
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
root.measure(canvas.getWidth(), canvas.getHeight());
root.layout(0, 0, canvas.getWidth(), canvas.getHeight());
root.draw(canvas);
root.setLayerType(1, null);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
root.measure(w, h);
root.layout(0, 0, w, h);
}
public void setData() {
alViews = new ArrayList<>();
for (int i = 0; i < alTemp.size(); i++) {
if (frontBack == alTemp.get(i).getFrontback()) {
alViews.add(alTemp.get(i));
}
}
root = new RelativeLayout(mContext);
root.setLayoutParams(new LayoutParams(Constants.saveWidth, Constants.saveHeight));
ImageView imageView = new ImageView(mContext);
imageView.setLayoutParams(new LayoutParams(Constants.saveWidth, Constants.saveHeight));
imageView.setScaleType(ScaleType.FIT_XY);
if (frontBack == 0) {
if (mTemplates.getFrontTemplatetype() == 2) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageBitmap(Functions.setFrontTemplate(mContext, mTemplates, Constants.saveWidth, Constants.saveHeight));
}
} else if (mTemplates.getBackTemplatetype() == 2) {
imageView.setImageBitmap(bitmap2);
} else {
imageView.setImageBitmap(Functions.setBackTemplate(mContext, mTemplates, Constants.saveWidth, Constants.saveHeight));
}
root.addView(imageView);
RelativeLayout relativeLayout = new RelativeLayout(mContext);
relativeLayout.setLayoutParams(new LayoutParams(Constants.saveWidth, Constants.saveWidth));
root.addView(relativeLayout);
for (int i = 0; i < alViews.size(); i++) {
System.out.println("loop " + i);
RelativeLayout relLayout = new RelativeLayout(mContext);
ViewGroup.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
relLayout.setX((float) Math.round(alViews.get(i).getRlx() * scale));
relLayout.setY((float) Math.round(alViews.get(i).getRly() * scale));
relLayout.setRotation(alViews.get(i).getRlrotation());
relLayout.setLayoutParams(layoutParams);
if (alViews.get(i).getType() == 0) {
CustomTextView customTextView = new CustomTextView(mContext);
String stringBuilder = " " +
alViews.get(i).getText().trim() +
" ";
customTextView.setText(stringBuilder);
customTextView.setTextColor((alViews.get(i)).getTextcolor());
customTextView.setTextSize(0, (float) Math.round(((float) alViews.get(i).getTextsize()) * scale));
customTextView.setGravity(alViews.get(i).getGravity());
customTextView.setTypeface(alViews.get(i).getTextstyle());
customTextView.setAlpha(((float) alViews.get(i).getOpacity()) / 100.0f);
if (alViews.get(i).isTextunderline()) {
customTextView.setPaintFlags(customTextView.getPaintFlags() | 8);
} else {
customTextView.setPaintFlags(customTextView.getPaintFlags() & -9);
}
if (alViews.get(i).isStroke()) {
customTextView.setStroke(true);
customTextView.setStrokeColor(alViews.get(i).getStrokeColor());
customTextView.setStrokeWidth(Math.round(((float) alViews.get(i).getStrokeWidth()) * scale));
customTextView.invalidate();
}
if (alViews.get(i).isShadow()) {
customTextView.setShadow(true);
customTextView.setShadowWidth(Math.round(alViews.get(i).getShadowSize() * scale));
customTextView.setShadowX(Math.round(alViews.get(i).getShadowX() * scale));
customTextView.setShadowY(Math.round(alViews.get(i).getShadowY() * scale));
customTextView.setShadowColor(alViews.get(i).getShadowColor());
customTextView.invalidate();
}
customTextView.setPadding(0, 0, (int) customTextView.getShadowX(), (int) customTextView.getShadowY());
customTextView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
relativeLayout.setClipToPadding(true);
relativeLayout.addView(customTextView);
} else if (alViews.get(i).getType() == 1 ||alViews.get(i).getType()==2) {
imageView = new ImageView(mContext);
imageView.setImageBitmap(alViews.get(i).getImagebitmap());
imageView.setAlpha(((float) alViews.get(i).getOpacity()) / 100.0f);
if (alViews.get(i).getColorFilter() != -1) {
imageView.setColorFilter((alViews.get(i)).getColorFilter());
}
LayoutParams layoutParams2 = new LayoutParams(Math.round((((float) alViews.get(i).getIvSize()) * scale) * alViews.get(i).getScaleW()), Math.round((((float) alViews.get(i).getIvSize()) * scale) * alViews.get(i).getScaleH()));
layoutParams2.setMargins(Math.round(mContext.getResources().getDimension(R.dimen.image_margin) * scale), Math.round(mContext.getResources().getDimension(R.dimen.image_margin) * scale), Math.round(mContext.getResources().getDimension(R.dimen.image_margin) * scale), Math.round(mContext.getResources().getDimension(R.dimen.image_margin) * scale));
imageView.setLayoutParams(layoutParams2);
relativeLayout.addView(imageView);
}
root.addView(relativeLayout);
}
}
}
I believe your issue is that you're using the relativeLayout variable within your loop rather than relLayout
Which means that relativeLayout is added to it's parent once for each loop iteration plus before the loop.
I believe you need
...
RelativeLayout relativeLayout = new RelativeLayout(mContext);
relativeLayout.setLayoutParams(new LayoutParams(Constants.saveWidth, Constants.saveWidth));
root.addView(relativeLayout);
for (int i = 0; i < alViews.size(); i++) {
System.out.println("loop " + i);
RelativeLayout relLayout = new RelativeLayout(mContext);
ViewGroup.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
relLayout.setX((float) Math.round(alViews.get(i).getRlx() * scale));
relLayout.setY((float) Math.round(alViews.get(i).getRly() * scale));
relLayout.setRotation(alViews.get(i).getRlrotation());
relLayout.setLayoutParams(layoutParams);
if (alViews.get(i).getType() == 0) {
...
relLayout.setClipToPadding(true);
relLayout.addView(customTextView);
}
...
root.addView(relLayout);
}
}
However, from looking at your code it looks as though it might be easier for you to define your layout as an XML file and load it into your view.
It looks as though you're rendering views for each item in the allTemp array. This really looks like a good use of a recycler view which I believe would serve you much better.
This might help: https://developer.android.com/guide/topics/ui/layout/recyclerview
I have GridView widget which is populated by 40 images taken from ArrayList, problem is when widget is trying to load more positions than 40 and it makes about few hundreds of blank cells with "loading" text. This wouldn't happen if number of items was fixed by 40, but how to do that?
This is my RemoteViewsFactory class:
public class WidgetRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private Context ctx;
private Cursor cursor;
private ArrayList<Bitmap> photos = new ArrayList<>(40);
public WidgetRemoteViewsFactory(Context applicationContext, Intent intent) {
ctx = applicationContext;
}
#Override
public void onCreate() {
}
#Override
public void onDataSetChanged() {
String[] projection = new String[]{
MediaStore.Images.ImageColumns._ID,
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.MIME_TYPE
};
cursor = ctx.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null,
null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC");
cursor.moveToFirst();
for (int i = 1; i <= 39; i++) {
Bitmap bmp = BitmapFactory.decodeFile(cursor.getString(1));
photos.add(bmp);
cursor.moveToNext();
Log.d(TAG, "loop iteration" + i );
}
}
#Override
public void onDestroy() {
if (cursor != null) {
cursor.close();
}
}
#Override
public int getCount() {
return cursor == null ? 0 : cursor.getCount();
}
#Override
public RemoteViews getViewAt(int position) {
if (position == AdapterView.INVALID_POSITION ||
cursor == null || !cursor.moveToPosition(position) || photos == null || photos.size() == 0 || position >= photos.size()) {
return null;
}
RemoteViews views = new RemoteViews(ctx.getPackageName(), R.layout.widget_item);
Bitmap img = resizeBitmapFitXY(250, 150, photos.get(position));
views.setImageViewBitmap(R.id.imageView, img);
return views;
}
#Override
public RemoteViews getLoadingView() {
return null;
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public long getItemId(int position) {
return cursor.moveToPosition(position) ? cursor.getLong(0) : position;
}
#Override
public boolean hasStableIds() {
return true;
}
public Bitmap resizeBitmapFitXY(int width, int height, Bitmap bitmap){
Bitmap background = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
float originalWidth = bitmap.getWidth(), originalHeight = bitmap.getHeight();
Canvas canvas = new Canvas(background);
float scale, xTranslation = 0.0f, yTranslation = 0.0f;
if (originalWidth > originalHeight) {
scale = height/originalHeight;
xTranslation = (width - originalWidth * scale)/2.0f;
}
else {
scale = width / originalWidth;
yTranslation = (height - originalHeight * scale)/2.0f;
}
Matrix transformation = new Matrix();
transformation.postTranslate(xTranslation, yTranslation);
transformation.preScale(scale, scale);
Paint paint = new Paint();
paint.setFilterBitmap(true);
canvas.drawBitmap(bitmap, transformation, paint);
return background;
}
}
you need to replace this code
cursor.moveToFirst();
for (int i = 1; i <= 39; i++) {
Bitmap bmp = BitmapFactory.decodeFile(cursor.getString(1));
photos.add(bmp);
cursor.moveToNext();
Log.d(TAG, "loop iteration" + i );
}
and put this instead
if (cursor!=null && cursor.getCount()>0) {
while (cursor.moveToNext()) {
Bitmap bmp = BitmapFactory.decodeFile(cursor.getString(1));
photos.add(bmp);
Log.d(TAG, "loop iteration" + i );
}
}
the problem that you used a fixed size in the for loop for (int i = 1; i <= 39; i++)
and the solution is to use while() loop to get all the item from the cursor and add it to your list
update answer after the comment :
Q: if you decide in the loop to load only 40 and not to load the blank text
you must to do this
#Override
public int getCount() {
return 40;
}
I have a gridview that contains an array of images in my drawable folder. I have it worked out right now to send the drawable to another activity where the user will view the image before setting a picture from the raw folder as the wallpaper. I can't use the drawable asset because of compression and a suitable image cause a crash from a lack of memory.
My MainActivity file with the gridview:
GridView androidGridView;
private Integer asset1 = R.drawable.asset1;
private Integer asset2 = R.drawable.asset2;
private Integer asset3 = R.drawable.asset1;
private Integer asset4 = R.drawable.asset2;
private Integer asset5 = R.drawable.asset1;
private Integer asset6 = R.drawable.asset2;
private Integer[] images = {
asset1, asset2, asset3,
asset4, asset5, asset6
};
Integer[] imagesIDs = {
R.raw.asset1, R.raw.asset2, R.drawable.asset1,
R.drawable.asset1, R.drawable.asset1, R.drawable.asset1,
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
androidGridView = findViewById(R.id.gridview_android_example);
androidGridView.setAdapter(new ImageAdapterGridView(this));
androidGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent,
View v, int position, long id) {
int imageRes = images[position];
Intent intent = new Intent(MainActivity.this, ViewActivity.class);
intent.putExtra("IMAGE_RES", imageRes);
startActivity(intent);
}
});
}
public class ImageAdapterGridView extends BaseAdapter {
private Context mContext;
public ImageAdapterGridView(Context c) {
mContext = c;
}
public int getCount() {
return images.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView mImageView;
if (convertView == null) {
mImageView = new ImageView(mContext);
mImageView.setLayoutParams(new GridView.LayoutParams(525, 350));
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
mImageView.setPadding(16, 16, 16, 16);
} else {
mImageView = (ImageView) convertView;
}
mImageView.setImageResource(images[position]);
return mImageView;
}
My ViewActivity file where the user will preview the image before setting it as the wallpaper:
private Integer asset1 = R.raw.asset1;
private Integer asset2 = R.raw.asset2;
private Integer asset3 = R.raw.asset1;
private Integer asset4 = R.raw.asset2;
private Integer asset5 = R.raw.asset1;
private Integer asset6 = R.raw.asset2;
private Integer[] images = {
asset1, asset2, asset3,
asset4, asset5, asset6
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
Bundle extras = getIntent().getExtras();
int imageRes = extras.getInt("IMAGE_RES");
ImageView preview = findViewById(R.id.preview);
preview.setImageResource(imageRes);
preview.setScaleType(ImageView.ScaleType.CENTER_CROP);
Button set = findViewById(R.id.setButton);
set.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
}
I'm not sure whether or not I'm on the right track, but if anyone can point me in the right direction that would be great!
A lot has been written on SO about Out of Memory errors when working with bitmaps and images in android apps: here, here and here, for example.
For the special purpose of setting the wallpaper on a device, you might try this sort of approach. I don't guarantee that you'll always avoid OOM errors doing it this way, but it should prevent most of them.
It does that by trying to stay within the app's current free memory when it decodes the resource into a bitmap. It also recycles the bitmap at the end.
One advantage is that you don't have to come up with the required width and height of the output bitmap. It does that for you, based on free memory. (That's also a disadvantage -- you're not free to choose whatever bitmap dimensions you want. They might be too large and cause a crash.)
It can take some time to do the decoding, which is why it's done on a background thread.
Anyway, this works for me:
Add an ExecutorService and the method decodeBitmapWithinFreeMemory to your ViewActivity:
private ExecutorService executor = Executors.newSingleThreadExecutor();
...
// adapted from https://developer.android.com/topic/performance/graphics/load-bitmap.html
private Bitmap decodeResourceWithinFreeMemory(Resources resources, int resourceId, float requiredAspectRatio) {
// get just the size of the resource image
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, resourceId, options);
// estimate number of pixels we can work with in current free memory
long freeMem = Runtime.getRuntime().freeMemory();
long spaceForARGV8888Px = freeMem / 4; // est. number of ARGV_8888 pixels that can be stored
// calculate the sides of a rectangle with approximately that number of pixels
long squareRootLowerBound = (long) Math.floor(Math.pow(spaceForARGV8888Px, 0.5));
int requestedWidth = (int) Math.floor(squareRootLowerBound * requiredAspectRatio);
int requestedHeight = (int) Math.floor(squareRootLowerBound / requiredAspectRatio);
// find the right sample size by aggressively increasing sampleSize var: require only that
// _one_ of the output dimensions be greater than the corresponding requested dimension
int sampleSize = 1;
while ((options.outHeight / (2 * sampleSize) ) >= requestedHeight
|| (options.outWidth / (2 * sampleSize) ) >= requestedWidth) {
sampleSize *= 2;
}
// output the bitmap by sampling the input resource at the calculated sampleSize
options.inSampleSize = sampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(resources, resourceId, options);
}
Invoke decodeBitmapWithinFreeMemory inside the button's onClick method, feeding it the device's screen aspect ratio:
DisplayMetrics metrics = getResources().getDisplayMetrics();
final float screenAspectRatio = (float)metrics.widthPixels/(float)metrics.heightPixels;
executor.submit(new Runnable() {
#Override
public void run() {
try {
Bitmap drawableAsBitmap = decodeResourceWithinFreeMemory(getResources(),
R.raw.asset1, screenAspectRatio);
WallpaperManager.getInstance(MainActivity.this).setBitmap(drawableAsBitmap);
drawableAsBitmap.recycle();
} catch (IOException ioe) {
Log.e(TAG, "Could not set wallpaper to bitmap", ioe);
}
}
});
Also note that you can optionally add a BroadcastReceiver to your Activities to be notified that the wallpaper has been set. (See the documentation for setBitmap.)
so i am trying now for about a week to get this thing working but to no avail....
i want the user to pick an image from his gallery and then crop that image into a nice circle profile photo i get the photo to crop but the background is still square.... i found this question and tried implementing the answer he gave but still the background is square Cropping circular area from bitmap in Android
i googled and just found more ways to do it but still gets the square background.... any help will be appreciated
public class RegisterActivity extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
RoundedImageView roundImageView ;
String numberToPass = "1";//default 1 for male
String selectedImagePath;
EditText etNickname, etAge;
Button btnNext;
ImageView profilePhoto, imageview;
Bitmap bitmap;
private OnRegisterListener mListener;
public RegisterActivity() {
// Required empty public constructor
}
public static RegisterActivity newInstance(String param1, String param2) {
RegisterActivity fragment = new RegisterActivity();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_register, container, false);
etNickname = (EditText) view.findViewById(R.id.etNickName);
btnNext = (Button) view.findViewById(R.id.btnNextRegister);
profilePhoto = (ImageView) view.findViewById(R.id.imageButton);
bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.blender);
// profilePhoto.setImageBitmap(bitmap);
/* Bitmap bitmap2 = BitmapFactory.decodeResource(this.getResources(),R.drawable.blender);
Bitmap circularBitmap = ImageConverter.getRoundedCornerBitmap(bitmap2, 100);
ImageView circularImageView = (ImageView)view.findViewById(R.id.imageButton);
circularImageView.setImageBitmap(circularBitmap); */
profilePhoto.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
openGallery();
}
});
return view;
}
// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnRegisterListener) {
mListener = (OnRegisterListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnRegisterListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnRegisterListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
//this allows the uset to select one image from openGallery
public void openGallery() {
Intent gallery = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI);
startActivityForResult(gallery, 1);
}
//when starting activity for result and choose an image, the code will automatically continue here
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == 1 && null != data) {
if (requestCode == 1) {
Uri current_ImageURI = data.getData();
/* String[] filePathColumn = { MediaStore.Images.Media.DATA };
Cursor cursor = getActivity().getContentResolver().query(current_ImageURI,
filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String picturePath = cursor.getString(columnIndex);
cursor.close();*/
selectedImagePath = getPath(current_ImageURI);
profilePhoto.setImageBitmap(circleBitmap(decodeSampledBitmap(new File(selectedImagePath), 250, 250)));
}
}
}
public String getPath(Uri contentUri) {
// we have to check for sdk version because from lollipop the retrieval of path is different
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// can post image
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = getActivity().getApplicationContext().getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} else {
String filePath = "";
String wholeID = DocumentsContract.getDocumentId(contentUri);
// Split at colon, use second item in the array
String id = wholeID.split(":")[1];
String[] column = {MediaStore.Images.Media.DATA};
// where id is equal to
String sel = MediaStore.Images.Media._ID + "=?";
Cursor cursor = getActivity().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, column, sel, new String[]{id}, null);
int columnIndex = cursor.getColumnIndex(column[0]);
if (cursor.moveToFirst()) {
filePath = cursor.getString(columnIndex);
}
cursor.close();
return filePath;
}
}
public Bitmap decodeSampledBitmap(File res, int reqWidth, int reqHeight) {
if (res != null) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
try {
FileInputStream stream2 = null;
try {
stream2 = new FileInputStream(res);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
BitmapFactory.decodeStream(stream2, null, options);
stream2.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// Calculate inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
o2.inJustDecodeBounds = false;
FileInputStream stream = null;
try {
stream = new FileInputStream(res);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, o2);
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
} else
return null;
}
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
//-----------------------------------------------------------------
private Bitmap circleBitmap(Bitmap bitmap) {
final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
int squareBitmapWidth = Math.min(bitmap.getWidth(), bitmap.getHeight());
/* Canvas
The Canvas class holds the "draw" calls. To draw something, you need 4 basic
components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing
into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint
(to describe the colors and styles for the drawing). */
final Canvas canvas = new Canvas(output);
final int color = Color.RED;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
// final Rect rect = new Rect(0, 0, squareBitmapWidth, squareBitmapWidth);
final RectF rectF = new RectF(rect);
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint);
//canvas.drawOval(rectF, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//float left = (squareBitmapWidth - bitmap.getWidth()) / 2;
//float top = (squareBitmapWidth - bitmap.getHeight()) / 2;
//canvas.drawBitmap(bitmap, left, top, paint);
canvas.drawBitmap(bitmap, rect, rect, paint);
//bitmap.recycle();
return output;
}
//---------------------------------------------------------------------------------------------
//end img upload
I use this code and it works perfectly:
public static Bitmap getCircularBitmap(Bitmap bitmap) {
Bitmap output;
if (bitmap.getWidth() > bitmap.getHeight()) {
output = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
} else {
output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getWidth(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
float r = 0;
if (bitmap.getWidth() > bitmap.getHeight()) {
r = bitmap.getHeight() / 2;
} else {
r = bitmap.getWidth() / 2;
}
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawCircle(r, r, r, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
You can custom ImageView to make its all drawing things circular. Here is my implementation(not the best solution for performance-eager application but good enough for condition that don't invalidate ImageView much):
class CircleImageView extends ImageView {
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleImageView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int radiusMeasureSpec = widthMeasureSpec;
super.onMeasure(radiusMeasureSpec, radiusMeasureSpec);
int radiusMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(radiusMeasureSize, radiusMeasureSize);
}
#Override
public void draw(Canvas viewCanvas) {
final int EDGE_SIZE = viewCanvas.getWidth();
// Draw this View's things.
Bitmap fgBm = Bitmap.createBitmap(EDGE_SIZE, EDGE_SIZE, Bitmap.Config.ARGB_8888);
Canvas fgCanvas = new Canvas(fgBm);
super.draw(fgCanvas);
// Transfer to a special shape.
Bitmap shapedBm = Bitmap.createBitmap(EDGE_SIZE, EDGE_SIZE, Bitmap.Config.ARGB_8888);
Canvas shapedCanvas = new Canvas(shapedBm);
shapedCanvas.drawCircle(EDGE_SIZE/2, EDGE_SIZE/2, EDGE_SIZE/2, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
shapedCanvas.drawBitmap(fgBm, 0, 0, mPaint);
mPaint.setXfermode(null);
// Move drawn things to View's canvas.
viewCanvas.drawBitmap(shapedBm, 0, 0, mPaint);
fgBm.recycle();
shapedBm.recycle();
}
}
There's someone who customizes ImageView using BitmapShader in an SO post without Xfermode and create extra Bitmap instances. Here's his implementation.
you can use trusted library like This one
Adding circle image adding border to your image and some other feature