I wrote code to draw on a view. After it is done, how can I get the resulting image from the view. For example, in the code below, I want to get the the drawable (Image) from mCustomDrawableView. How can I do that? Thanks.
public class HelloTestGraph extends Activity {
/** Called when the activity is first created. */
// #Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout lo = (LinearLayout) findViewById(R.id.top_view);
LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
CustomDrawableView mCustomDrawableView = new CustomDrawableView(this);
mCustomDrawableView.setLayoutParams(param);
lo.addView(mCustomDrawableView);
}
public class CustomDrawableView extends View {
private ShapeDrawable mDrawable;
private Drawable mPic;
public CustomDrawableView(Context context) {
super(context);
int x = 10;
int y = 10;
int width = 300;
int height = 50;
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
mPic = getResources().getDrawable(R.drawable.example_picture);
mPic.setBounds(x, y + 100, x + width, y + height+100);
}
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
mPic.draw(canvas);
}
}
}
This is a little convoluted, but should get you there.
Step 1: Create a Muteable Bitmap of the size you want and stash it aside. It could be the size of the device's screen or the size of your largest view (depending on what you want to do with it later) Save that bitmap pointer aside as something like myBitmap
Step 2: Create a canvas with the aforementioned bitmap. "Canvas myCanvas = new Canvas(myBitmap);"
Step 3: In your onDraw() method, draw your views both to the passed in "canvas" object, and to your own custom one.
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
mPic.draw(canvas);
mDrawable.draw(myCanvas);
mPic.draw(myCanvas);
}
Step 4: Your original bitmap should now contain the fully rendered version of ONLY your view.
I'm not sure if this is exactly what you're looking for, but it will give you a bitmap (which you can convert into an image) of the contents of your view.
From inside your custom class, you'd specify getters and setters if you wanted to access them from outside the class.
public Drawable getDrawable() { return mDrawable; }
Then from outside the class (as in your activity), you can call the getDrawable() method on the view once it's instantiated.
Drawable drawable = mCustomDrawableView.getDrawable();
Related
I came across a weird issue of the android framework again:
I have an activity which displays detailed information on an object. It is designed to look like a "floating" activity, meaning it overlays the MainActivity and can be dismissed by a simple swipe down from the user.
Screenshot
How it's done (wrong?)
Because setting the window background to #android:color/transparent lead to ugly side effects, I'm using a custom ImageView as the background (I modified this one by Chris Banes https://github.com/chrisbanes/philm/blob/master/app/src/main/java/app/philm/in/view/BackdropImageView.java):
public class BackdropImageView extends ImageView {
private static final int MIN_SCRIM_ALPHA = 0x00;
private static final int MAX_SCRIM_ALPHA = 0xFF;
private static final int SCRIM_ALPHA_DIFF = MAX_SCRIM_ALPHA - MIN_SCRIM_ALPHA;
private float mScrimDarkness;
private float factor;
private int mScrimColor = Color.BLACK;
private int mScrollOffset;
private int mImageOffset;
private final Paint mScrimPaint;
public BackdropImageView(Context context) {
super(context);
mScrimPaint = new Paint();
factor = 2;
}
public BackdropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mScrimPaint = new Paint();
factor = 2;
}
private void setScrollOffset(int offset) {
if (offset != mScrollOffset) {
mScrollOffset = offset;
mImageOffset = (int) (-offset / factor);
offsetTopAndBottom(offset - getTop());
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void setFactor(float factor) {
this.factor = factor;
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mScrollOffset != 0) {
offsetTopAndBottom(mScrollOffset - getTop());
}
}
public void setScrimColor(int scrimColor) {
if (mScrimColor != scrimColor) {
mScrimColor = scrimColor;
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void setProgress(int offset, float scrim) {
mScrimDarkness = ScrollUtils.getFloat(scrim, 0, 1);
setScrollOffset(offset);
}
#Override
protected void onDraw(#NonNull Canvas canvas) {
// Update the scrim paint
mScrimPaint.setColor(ColorUtils.setAlphaComponent(mScrimColor,
MIN_SCRIM_ALPHA + (int) (SCRIM_ALPHA_DIFF * mScrimDarkness)));
if (mImageOffset != 0) {
canvas.save();
canvas.translate(0f, mImageOffset);
canvas.clipRect(0f, 0f, canvas.getWidth(), canvas.getHeight() + mImageOffset + 1);
super.onDraw(canvas);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mScrimPaint);
canvas.restore();
} else {
super.onDraw(canvas);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mScrimPaint);
}
}
}
When I start the activity, I create a snapshot of the current activity and then save it to cache, passing it's path through Intent.putExtra(String key, String value); :
public static Intent createOverlayActivity(Activity activity) {
Intent startIntent = new Intent(activity, OverlayActivity.class);
View root = activity.findViewById(android.R.id.content);
Rect clipRect = new Rect();
activity.getWindow().getDecorView()
.getWindowVisibleDisplayFrame(clipRect);
Bitmap bitmap = Bitmap.createBitmap(
root.getWidth(),
root.getHeight(),
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawRGB(0xEE, 0xEE, 0xEE);
// Quick fix for status bar appearing in Lollipop and above
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.translate(0, -clipRect.top / 2);
canvas.clipRect(clipRect);
}
root.draw(canvas);
try {
File file = new File(activity.getCacheDir(), "background.jpg");
FileOutputStream stream = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream);
stream.flush();
stream.close();
bitmap.recycle();
startIntent.putExtra("bgBitmap", file.getPath());
Log.d(TAG, "Rendered background image.");
} catch (IOException e) {
e.printStackTrace();
}
return startIntent;
}
And in the OverlayActivity's onCreate() I receive the path to the cached file and load the Bitmap into the ImageView:
Bitmap screenshot = BitmapFactory.decodeFile(getIntent().getStringExtra("bgBitmap"));
if (screenshot == null) {
throw new IllegalArgumentException("You have to provide a valid bitmap!");
}
/* BackdropImageView is an ImageView subclass allowing
* me to darken the image with a scrim using the slide offset value */
backdropImageView = new BackdropImageView(this);
backdropImageView.setId(android.R.id.background);
backdropImageView.setFactor(1.125f);
backdropImageView.setScrimColor(Color.BLACK);
backdropImageView.setImageBitmap(screenshot);
backdropImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
The issue
As you can see in the screenshot above, it works pretty decent on devices running API 23, but not on devices below.
Here the image is shown when the activity slides in, until completely covered, but when I the slide down again, the image is gone and the ImageView just shows a solid grey:
Update
I've figured out that the issue has to hide somewhere in the BackdropImageView class, since a simple ImageView works.
Any ideas on what could cause this weird issue?
Thanks in advance!
Ok, seems like I focused too much on the actual image loading than on the view itself.
I found out that the problem was related to the use of offsetTopAndBottom(offset - getTop()); in the subclass. Somehow this makes the ImageView disappear sometimes (still not sure when exactly) on Android Versions previous to Marshmallow, although it was still in the right position in the view hierarchy.
So here's my work-around:
When I removed those lines of code, it worked. Because I needed to offset the view to create a parallax scrolling effect, I moved this functionality out of the view subclass. What I'm doing now is simply calling View.setTranslationY(float) everytime before BackdropImageView.setProgress(int, float) in any scrolling related callback I'm using.
Somehow this works perfectly fine.
I have an ImageView and I am trying to fade from one image to the next using this code:
Drawable bgs[] = new Drawable[2];
public void redraw(int[][] grid) {
bgs[0] = bgs[1];
bgs[1] = new GameDrawable(grid, prefs.colors);
if (bgs[0] == null) {
gameField.setImageDrawable(bgs[1]);
} else {
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
}
gameField is correctly referenced as an ImageView.
gameDrawable simply extends Drawable and draws the grid.
On each move and action the new GameDrawable is being rendered correctly but there is no fading whatsoever. The new image is simply displayed instantaneously. I have tried lengthening the transition time and swapping the order of the drawables with no effect.
Any help on is appreciated.
Update: I have now set my transition to something ridiculously long like 500000. The first drawable shows for a few seconds and then suddenly the second drawable appears. So still no transition.
Update 2:
I think my Drawable might be implemented incorrectly, so I have attached the code.
public class GameDrawable extends Drawable {
private Paint paint = new Paint();
private float blockWidth = 1;
private int[][] myGrid;
private int myColor;
private List<Point> myPoints;
public GameDrawable(int[][] grid) {
super();
this.myGrid = grid;
this.myColor = colors[yourColor];
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setAlpha(0);
this.myPoints = yourPoints;
}
#Override
public void draw(Canvas canvas) {
float height = getBounds().height();
float width = getBounds().width();
blockWidth = width / myGrid.length;
if (height / myGrid.length < blockWidth) {
blockWidth = height / myGrid.length;
}
for (int x = 0; x < myGrid.length; x++) {
for (int y = 0; y < myGrid[x].length; y++) {
paint.setColor(colors[myGrid[x][y]]);
canvas.drawRect(x * blockWidth, y * blockWidth, (x+1)*blockWidth, (y+1)*blockWidth, paint);
}
}
}
#Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
invalidateSelf();
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Looking at your code, I see a problem at the line
bgs[0] = bgs[1];
bgs[1] has not yet been defined before this line and so bgs[0] is null for the first method call. Because of this, (bgs[0] == null) is true, and so the later defined bgs[1] is directly set to the gameField ImageView.
Use corrected code below.
Drawable bgs[] = new Drawable[2];
Drawable firstDrawable = getResources().getDrawable(R.drawable.transparent);
public void redraw(int[][] grid) {
bgs[0] = firstDrawable;
bgs[1] = new GameDrawable(grid, prefs.colors);
firstDrawable = bgs[1];
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
Note that TransitionDrawable does not work properly when the Drawable sizes are different. So you may need to resize firstDrawable beforehand.
EXTRA: I would avoid setCrossFadeEnabled(true) since the whole TransitionDrawable becomes translucent during the transition, revealing the background. Sometimes, this creates a "blinking" effect and destroys the smoothness of the transition.
EDIT: Looking at your custom Drawable implementation, I think the problem lies in the line
canvas.drawColor(Color.WHITE);
in the draw() method.
I looked at TransitionDrawable.java source and found that setAlpha is called on the drawables to get the cross fade effect. However, your canvas has a solid white color and setAlpha() only affects the paint. Hope this is your answer.
EDIT 2: The actual problem, as pointed out by Michael, was that TransitionDrawable's setAlpha() calls on the Drawables were rendered ineffective due to paint.setColor() in the GameDrawable's draw() method overriding the paint's alpha value set by the TransitionDrawable.
Im extending the View class in order to have a "drawable" view witch contains the desired background.
The problem is when this class works fine in other activity (same app).
Here is the class that extends View:
public class BackgroundMap extends View {
private String strmap;
private int totalWidth;
public BackgroundMap(Context context, String map, int w, int h) {
super(context);
super.setLayoutParams(new LayoutParams(w, h));
totalWidth = w;
strmap = map;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Resources res = getResources();
int eachBoxSize = totalWidth/10;
float left = 0;
float top = 0;
Paint paint = new Paint();
for(char c : strmap.toCharArray()){
Bitmap bm = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(res, Terrain.getResource(c)), eachBoxSize, eachBoxSize, false);
canvas.drawBitmap(bm, left, top, paint);
if(left == totalWidth - eachBoxSize){
left = 0;
top += eachBoxSize;
}else{
left += eachBoxSize;
}
}
}
}
This view works like this: somelayout.addView(new BackgroundMap(arg..));
the String map argument means an array of terrains (char) ids that point to several little png images.
and this is how i use it in my Activity:
ly_rbtn_image = (FrameLayout) findViewById(R.id.ly_rbtn_image);
ly_rbtn_image.post(new Runnable() {
#Override
public void run() {
imagewidth = ly_rbtn_image.getWidth();
ly_rbtn_image.addView(new BackgroundMap(getApplicationContext(), str, imagewidth, imagewidth));
}
});
i check with logcat the width of parent layout and it has 225px. As you can see the image below only build the first row of bitmaps... but this IS NOT a loop problem
I check inside the custom view loop at onDraw method if there was a problem here but it iterate the whole string (100 chars, 10x10 bitmaps)
hope u can help me :S
http://deviantsart.com/1afcam8.png
solved. please delete me.
the bitmaps were pushed at right till the end of the loop. So in fact, i didnt know why this happened.
UNTIL I CHECKED THE BOUNDS OF THE RESULT VIEW IN LOGCAT x))) the view had 2000 pixels width and 20 height... there we go:
When i get the total width of the device, casually was a number divisible by 10 (10x10 grid) so in fact was lucky not having this problem before (building the same map in other view).
Then, the problem was the need to print the same map (or grid) in an other view, not big as the device width, so the total width (parent width) was not divisible by the number of columns of the grid (im ashamed x)).
I have the following class in an Android project:
public class Island extends View {
private ShapeDrawable mDrawable;
public Island(Context context) {
super(context);
int x = 0;
int y = 0;
int width = 50;
int height = 50;
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
public void change() {
mDrawable.getPaint().setColor(Color.BLACK);
}
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
}
}
Why am I not able to multiple circles to my layout? Say I have a vertical LinearLayout, then in my on create, I do:
layout.addView(new Island(this));
layout.addView(new Island(this));
Only one circle shows up instead of two different circles. I can't figure out why this is happening. Any idea what is happening to the other circle? Thanks for any ideas.
Edit:
I also tried adding the circles and setting their width and heights to wrap_content, and only one shows up still.
I am very new to Java and android. my 1st app using canvas and paint. for some reason I get a force close whenever I try using the drawText method.. Please help.
I am basically trying to display text in a specific x,y coordinate. which will need updates throughout game play, my code:
public class MyGame extends Main {
TextView timeDisplay;
public String clock;
int x_pos = 10;
int y_pos = 100;
int radius = 20;
float x = 10;
float y = 20;
android.graphics.Paint p;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// setup Drawing view
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
c.drawText("test", 30, 0,x,y, p); <-- if I comment this out, no force close...
Your help is appreciated.
Your Paint object "p" is never created. It contains the null pointer, hence the exception you are getting.
initialize p as follow
Paint p = new Paint();
p.setColor(Color.WHITE);
p.setStyle(Style.FILL);
and then use
c.drawText("test", 30, 0,x,y, p);