I'm developing an app for Android with beautiful, but hard-to-implement design with tons of custom animations. Now I need to implement animation for button, it must be dynamic glance animation that affects both button border and its text, and must move from left to right. Here is an example of such animation in iOS app.
So I hope you catched the main idea. I tried to use Property Animation, but it's not what I really need. Also I found many custom libraries and tried a lot of examples from SDK, but there are nothing similar.
So if you know how it can be do, answer me.
I get the pre-rendered animation with a sequence of PNG files and then use the AnimationDrawable to display it.
try this wrapper class:
class FL extends FrameLayout implements ValueAnimator.AnimatorUpdateListener {
private static final int W = 200;
private final LinearGradient gradient;
private final Paint paint;
private float x;
public FL(View child) {
super(child.getContext());
addView(child);
int[] colors = {0, 0xaaffffff, 0};
gradient = new LinearGradient(0, 0, W, 0, colors, null, Shader.TileMode.CLAMP);
paint = new Paint();
paint.setShader(gradient);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
x = -W;
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.saveLayer(null, null, 0);
super.dispatchDraw(canvas);
canvas.translate(x, 0);
float h = getHeight();
canvas.rotate(20, W / 2, h / 2);
canvas.drawRect(0, -h, W, 2 * h, paint);
canvas.restore();
}
#Override
public void onAnimationUpdate(ValueAnimator animation) {
x = (int) animation.getAnimatedValue();
invalidate();
}
public void startMagic() {
ValueAnimator a = ValueAnimator.ofInt(-W, getWidth());
a.addUpdateListener(this);
a.setDuration(1000).setInterpolator(new AccelerateDecelerateInterpolator());
a.start();
}
}
round_frame.xml is:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp" />
<stroke android:color="#a00" android:width="4dp" />
</shape>
and test it with the following in onCreate:
Button b = new Button(this);
final FL fl = new FL(b);
b.setTextSize(28);
b.setTextColor(0xffaa0000);
b.setText("click me to see the magic");
b.setBackgroundResource(R.drawable.round_frame);
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
fl.startMagic();
}
};
b.setOnClickListener(listener);
addContentView(fl, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
NOTE: if you want it to use it in the Button only, you don't have a FL wrapper: you can extend Button class and override drawing methods as i did in FL wrapper class
I finished my custom implementation of this.
The result so far :
This is obtained by transferring an animated gradient Shader on the static TextView
You can get the source code on the following Gist : ShinnyTextView
The current version does not support XML customization, but you can adapt the values in the code.
If you are using this, you might want to have a more precise control on when to pause/restart the animation to avoid the ani
Related
This is what I wish to achieve:
Clicky
The container color, the progress color, the progress background color and the rounded edge radius as well as the thickness should all be editable and modifiable.
How could this be achieved with a light weight custom UI element?
After days of research, I was able to achieve what was expected with clear crisp UI and with all the above requirements and flexibility. The exact above UI can be achieved and follow parameters can be achieved as well:
1. Progress Color
2. Progress background color
3. Container color (Color of container to be set by you, you can set color of rounded edges to match the container color)
4. Height and width of the progress bar to suit your needs.
Here's the code and steps to implement it:
I. Put this code in the attrs.xml file under the values folder
<declare-styleable name="SlantingProgressBar">
<attr name="slantingProgress" format="integer"/>
<attr name="borderRadius" format="integer"/>
<attr name="borderColor" format="integer"/>
<attr name="slantingProgressColor" format="string"/>
<attr name="progressBackgroundColor" format="string"/>
<attr name="slantingProgressFullColor" format="string"/>
</declare-styleable>
II. Create a java class like this:
public class SlantingProgressbar extends View {
private float height = 0;
private float width = 0;
private int borderRadius = 20;
private float progress = 0;
private int rawProgress = 0;
private static final String OPACITY_30_PERCENT = "#66";
private int roundedBorderColor;
private String backgroundColor = "";
private String progressColor = "";
private String progressFullColor = "#fc3d39";
public SlantingProgressbar(Context context) {
super(context);
}
public SlantingProgressbar(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SlantingProgressBar,
0, 0);
try {
setProgress(array.getInt(R.styleable.SlantingProgressBar_slantingProgress, 0));
setBackgroundColor(array.getString(R.styleable.SlantingProgressBar_progressBackgroundColor)); //Default color set in the method
setBorderRadius(array.getInt(R.styleable.SlantingProgressBar_borderRadius, 20));
setRoundedBorderColor(array.getInt(R.styleable.SlantingProgressBar_borderColor, 0));
setProgressColor(array.getString(R.styleable.SlantingProgressBar_slantingProgressColor));
} finally {
array.recycle();
}
}
public void setBorderRadius(int borderRadius) {
this.borderRadius = borderRadius;
}
public int getProgress() {
return rawProgress;
}
public void setProgress(int progress) {
if(progress >=0)
{
this.rawProgress = progress;
this.invalidate();
}
else
Log.e("ChlorophyllProgressBar", "Invalid 'progress' value detected, value should be between 0 and 100");
}
public void setRoundedBorderColor(int roundedBorderColor) {
if ( roundedBorderColor == 0) {
this.roundedBorderColor = getResources().getColor(R.color.white);
Log.e("CUSTOM_TAG", "Color set to White: " + this.roundedBorderColor);
return;
}
this.roundedBorderColor = roundedBorderColor;
Log.e("CUSTOM_TAG", "Color set to custom: " + this.roundedBorderColor);
}
private int getRoundedBorderColor()
{
return roundedBorderColor;
}
public void setSlantingProgressFullColor(String color)
{
if (TextUtils.isEmpty(progressFullColor)) {
this.progressFullColor = "#fc3d39";
return;
}
}
public void setBackgroundColor(String backgroundColor) {
if (TextUtils.isEmpty(backgroundColor)) {
this.backgroundColor = "#bfe8d4";
return;
}
this.backgroundColor = backgroundColor;
}
public void setProgressColor(String progressColor) {
if (TextUtils.isEmpty(progressColor)) {
this.progressColor = "#2bb673"; //Green
return;
}
this.progressColor = progressColor;
}
public float getViewHeight() {
return height;
}
public void setViewHeight(float height) {
this.height = height;
}
public float getViewWidth() {
return width;
}
public void setViewWidth(float width) {
this.width = width;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
height = getHeight();
width = getWidth();
progress = getProcessedProgress();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.parseColor(backgroundColor));
canvas.drawPaint(paint);
paint.setColor(getProcessedProgressColor());
paint.setAntiAlias(true);
Log.d("CUSTOM_TAG", "Height: " + height);
canvas.drawRect(0, 0, progress, height, paint);
Path triangle = new Path();
triangle.setFillType(Path.FillType.EVEN_ODD);
triangle.moveTo(progress, 0);
triangle.lineTo(progress + height, 0);
triangle.lineTo(progress, height);
triangle.close();
canvas.drawPath(triangle, paint);
drawBorders(canvas, getRoundedBorderColor());
}
private void drawBorders(Canvas canvas, int color) {
float height = getHeight();
float trueWidth = getWidth();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
//paint.setColor(getResources().getColor(R.color.white));
paint.setAntiAlias(true);
Path border = new Path();
border.moveTo(0, 0);
border.lineTo(0, height / 2);
border.quadTo(height / borderRadius, height / borderRadius, height / 2, 0);
border.lineTo(0, 0);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(0, height);
border.lineTo(height / 2, height);
border.quadTo(height / borderRadius, (height - height / borderRadius), 0, height / 2);
border.lineTo(0, height);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(trueWidth, 0);
border.lineTo(trueWidth - (height / 2), 0);
border.quadTo((trueWidth - height / borderRadius), height / borderRadius, trueWidth, height / 2);
border.lineTo(trueWidth, 0);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(trueWidth, height);
border.lineTo(trueWidth - (height / 2), height);
border.quadTo((trueWidth - height / borderRadius), (height - height / borderRadius), trueWidth, height / 2);
border.lineTo(trueWidth, height);
canvas.drawPath(border, paint);
//Adding 1 pixel color
Paint paint1 = new Paint();
paint1.setStyle(Paint.Style.FILL);
int fadedColor = (color & 0x00FFFFFF) | 0x66000000;
Log.d("CUSTOM_TAG", "Faded Color Code: " + fadedColor);
paint1.setColor(fadedColor);
canvas.drawRect(0, 0, 1, height, paint1);
canvas.drawRect(trueWidth-1, 0, trueWidth, height, paint1);
}
private float getProcessedProgress()
{
return (rawProgress == 99) ? ((getWidth() * 98) / 100) : ((getWidth() * rawProgress) / 100);
}
private int getProcessedProgressColor()
{
if(rawProgress > 100)
{
return Color.parseColor(progressFullColor);
}
else
{
return Color.parseColor(progressColor);
}
}
}
III. To use the layout in your xml file:
<com.whatever.package.SlantingProgressbar
android:id="#+id/progressbar_detail"
android:layout_width="match_parent"
android:layout_height="#dimen/dimension1"
android:layout_alignParentLeft="true"
slanting_progress:borderColor="#color/darkgray"
android:layout_below="#id/alphacon_detail"
android:layout_marginBottom="#dimen/budget_list_item_paddingBottom"
android:progress="50" />
I'm sharing this code after a little while, so I might have missed out a thing or two, I'm pretty sure you can get that worked out, please feel free to correct me.
Explanation:
We're using the 'draw' methods in java to implement this feature. The advantage is that, drawing a UI element gives us a sharp and clear UI no matter how big or small you make it.
There might be some hardcoded values, so be sure to edit those before implementing.
Good luck and don't forget to up-vote if this post helps you. Thanks! :)
I'll post an answer here to show some improvements on your code.
You should avoid creating new objects during draw.
draw is called several times again and again to redraw your custom element and all those calls to new Paint() are creating new objects, that needs new memory allocation, and it drives the garbage collector crazy and makes your View much more resource intensive and probably will cause lag on scrolling elements such as RecyclerView.
Alternatively you should have them declared as private Paint border and then private Paint triangle, etc, etc. And then you should initialise the values of All the paints in a separate method and only if the parameters changed. An example code:
private boolean initPaint = false;
private void initPaintsIfNecessary(){
if(!initPaint) return;
initPaint = false;
triangle = new Paint();
triangle.set.... etc
border = new Paint();
border.set.... etc
}
then on all the methods setRoundedBorderColor, setProgressColor, etc. You call initPaint = true; and on the beginning of draw you call initPaintsIfNecessary();. This will avoid all the extra garbage collector work and will allow the UI of your app to run much smoother.
That also includes all the Paint inside drawBorders method.
use format="color" instead ofstring`.
Calling Color.parse(String) is a very slow call and it is very error prone. Alternatively you should the correct color element, like following:
<attr name="slantingProgressColor" format="color"/>
that not just is the correct way, but gives you a color preview on the editor, can be indexed on app style parameters and avoid this inefficient call to parse
then of course you should adjust or method appriately. For example:
setProgressColor(array.getColor(R.styleable.SlantingProgressBar_slantingProgressColor));
getColor will return an integer that can be directly used in paint.setColor(int);
I hope those tips can help you (and others in the community) to create better more efficient View elements. Happy coding!
I know this this old question to answer but this answer may helpful..
You can use drawArc method to achieve this..
RectF oval = new RectF();
oval.set(left, top ,right, bottom);
canvas.drawArc(oval, 270, 360, false, paint);
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.
I want to show images with alphabets like gmail app as shown in the below figure.
Are all those images are images to be kept in drawable folder or they are drawn as square shapes and then letters are drawn to them? Below is what I tried so far to do dynamically. I got just a square shape. Can someone suggest the way to achieve like in gmail app?
GradientDrawable gd = new GradientDrawable();
gd.mutate();
gd.setColor(getResources().getColor(gColors[i]));
button.setBackgroundDrawable(gd);
Update 2:
I have fixed some of the bugs and released the code as an open source library at: https://github.com/amulyakhare/TextDrawable. It also include some other features that you might want to check out.
Old Answer:
I recommend you to use the following class CharacterDrawable (just copy-paste this):
public class CharacterDrawable extends ColorDrawable {
private final char character;
private final Paint textPaint;
private final Paint borderPaint;
private static final int STROKE_WIDTH = 10;
private static final float SHADE_FACTOR = 0.9f;
public CharacterDrawable(char character, int color) {
super(color);
this.character = character;
this.textPaint = new Paint();
this.borderPaint = new Paint();
// text paint settings
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
textPaint.setFakeBoldText(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextAlign(Paint.Align.CENTER);
// border paint settings
borderPaint.setColor(getDarkerShade(color));
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(STROKE_WIDTH);
}
private int getDarkerShade(int color) {
return Color.rgb((int)(SHADE_FACTOR * Color.red(color)),
(int)(SHADE_FACTOR * Color.green(color)),
(int)(SHADE_FACTOR * Color.blue(color)));
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// draw border
canvas.drawRect(getBounds(), borderPaint);
// draw text
int width = canvas.getWidth();
int height = canvas.getHeight();
textPaint.setTextSize(height / 2);
canvas.drawText(String.valueOf(character), width/2, height/2 - ((textPaint.descent() + textPaint.ascent()) / 2) , textPaint);
}
#Override
public void setAlpha(int alpha) {
textPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
textPaint.setColorFilter(cf);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Then using this is simple: new CharacterDrawable('A', 0xFF805781); by passing the character and the color value (example Color.RED or some other color in hex 0xFF805781):
ImageView imageView = (ImageView) findViewById(R.id.imageView);
CharacterDrawable drawable = new CharacterDrawable('A', 0xFF805781);
imageView.setImageDrawable(drawable);
or based on your question:
CharacterDrawable drawable = new CharacterDrawable('A', 0xFF805781);
button.setBackgroundDrawable(drawable);
The drawable will scale to fit the size of the ImageView. Result will be:
Update: Updated code for adding a border which is of darker shade (automatically picks a dark shade based on the fill color).
1) Change the value of STROKE_WIDTH based on your needs for the border thikness.
2) Change the value of SHADE_FACTOR for border darkness. If SHADE_FACTOR is small (eg. 0.2f), the border will be darker and vice versa.
Note: You can easily vary the size and font of the character
Simple thing is that you have use Linear Layout and set that background color and set TectView inside that root layout. Its Over.
You should use ColorCode Intesed of images that will good thing compare to use images in terms of loading on UI thread.
<LinearLayout
android:id="#+id/get_more"
android:layout_width="70dp" // this root layout will set your square
android:layout_height="70dp"
android:background="#654321" // set background color of square
android:orientation="horizontal" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="24sp"
android:text="C"
android:background="#ffffff" // Text Color , set as White
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
I tweak the code a little bit..., and it works everytime even with different screen sizes. The trick is to obtain the ImageView canvas size in pixels (which sometimes is density dependent on various devices)
package net.mypapit.android.ui;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
public class CharacterDrawable extends ColorDrawable {
private final char character;
private final Paint textPaint;
private final Paint borderPaint;
private static final int STROKE_WIDTH = 10;
private static final float SHADE_FACTOR = 0.9f;
private int mwidth, mheight;
public CharacterDrawable(char character, int color, int width, int height) {
super(color);
this.character = character;
this.textPaint = new Paint();
this.borderPaint = new Paint();
this.mwidth = width;
this.mheight = height;
// text paint settings
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
textPaint.setFakeBoldText(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextAlign(Paint.Align.CENTER);
// border paint settings
borderPaint.setColor(getDarkerShade(color));
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(STROKE_WIDTH);
}
private int getDarkerShade(int color) {
return Color.rgb((int)(SHADE_FACTOR * Color.red(color)),
(int)(SHADE_FACTOR * Color.green(color)),
(int)(SHADE_FACTOR * Color.blue(color)));
}
public void draw(Canvas canvas) {
super.draw(canvas);
// draw border
canvas.drawRect(getBounds(), borderPaint);
// draw text
int width = this.mwidth;
int height = this.mheight;
textPaint.setTextSize(height / 2);
canvas.drawText(String.valueOf(character), width/2, height/2 - ((textPaint.descent() + textPaint.ascent()) / 2) , textPaint);
}
public void setAlpha(int alpha) {
textPaint.setAlpha(alpha);
}
public void setColorFilter(ColorFilter cf) {
textPaint.setColorFilter(cf);
}
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Then, refer back to the original Amulya Khare answer:
ImageView imageView = (ImageView) findViewById(R.id.imageView);
CharacterDrawable drawable = new CharacterDrawable('A', 0xFF805781,imageView.getWidth(),imageView.getHeight());
imageView.setImageDrawable(drawable);
It should work on different screen density by now =)
I'm trying to create an AndEngine HUD that will sit on top of a TMXTiledMap (bear with me, I'm very new to AndEngine). To keep things simple at first, I have a simple rectangle created via Android drawables. The idea is that this will sit at the bottom center of the screen and never move, even as the map underneath it is moved in various directions. For now, all I want to do is get that rectangle to show up. Later on I'll add other functionality to the rectangle.
My drawable is created like this:
?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<corners
android:radius="7dp" />
<gradient
android:startColor="#343434"
android:endColor="#17171717"
android:angle="270"
android:useLevel="false"
android:type="linear" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
And I have it pulled into a layout like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:layout_width="300dip"
android:layout_height="100dip"
android:orientation="horizontal"
android:layout_marginLeft="20dp"
android:background="#drawable/menu_bkgrnd" >
</LinearLayout>
And then finally, here is where I try to pull it in as a HUD:
rect = new HUD();
ITouchArea container = (ITouchArea) findViewById(R.id.container);
this.rect.registerTouchArea(container);
rect.attachChild((IEntity) container);
As you can see, I'm doing a lot of casting to satisfy AndEngine, but when I run this, the map is totally screwed up. Am I going about this correctly? Is my casting incorrect? (or maybe both!).
Thanks for any help.
EDIT:
Based on the code that Jong and 正宗白布鞋 suggested below, I've adjusted my Java code as follows:
this.atlas = new BitmapTextureAtlas(null, 256, 256, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
this.atlas.load();
ITextureRegion drawable = BitmapTextureAtlasTextureRegionFactory.createFromResource(atlas, getApplicationContext(), R.drawable.myDrawable, 0, 0);
rect.attachChild(new Sprite(0, 0, drawable, this.getVertexBufferObjectManager()));
At this point, I'm still just trying to get this to appear on the screen. I'll adjust size and location later.
Everything compiles and runs without error, however my screen is just a total mess.
As you can see, I had to make a couple of small adjustments to the constructor arguments to get AndEngine to accept my instantiations. Not sure if I'm doing this correctly.
The other issue that I see in this code is that it appears to me that this code is just going to place an inactive shape on my screen. I know that in my original post, I said that my immediate goal is to make this rectangle show up, but I think that it has to show up as a registered touch area since it will ultimately be something with controls on it that need to respond to user commands. Sorry if I overly minimized what I am trying to do.
I'm still not getting this. Anybody have any suggestions? Thanks again!
You can't cast LinearLayout to ITouchArea, ITouchArea is an interface implemented by AndEngine classes only.
Like 正宗白布鞋 suggested, you should use the createFromResource method of BitmapTextureAtlasTextureRegionFactory.
You can use this code:
BitmapTextureAtlas atlas = new BitmapTextureAtlas(sizeX, sizeY, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
TextureRegion drawable = BitmapTextureAtlasTextureRegionFactory.createFromResource(atlas, getApplicationContext(), R.drawable.drawableId, 0, 0);
rect.attachChild(new Sprite(x, y, drawable));
EDIT:
If you want your sprite to respond to touch events, you can register it as a touch area in your rect HUD.
Sprite sprite = new Sprite(x, y, drawable, getVertexBufferObjectManager()) {
#Override
public boolean onAreaTouched(final TouchEvent pTouchEvent, final float pX, final float pY) {
//Do what you want here
}
}
rect.registerTouchArea(sprite);
rect.attachChild(sprite);
I had the same problem several days ago, and I couldn't find a proper answer. So now that I've gathered information from different sources, here is how I dealt with this.
AFAIK, it appears that the method createFromResource(atlas, getApplicationContext(), R.drawable.drawableId, 0, 0) from BitmapTextureAtlasTextureRegionFactory cannot deal with XML drawable (such as <shape>...</shape>).
Indeed, when I was only displaying a colored rectangle, everything was fine - and I think with a PNG file too -, but when I tried to create a Sprite from my XML drawable, I always got a NullBitmapException when the drawing happened.
Steps I took:
Convert drawable resource to Drawable
Convert Drawable to Bitmap (Source here)
Convert Bitmap to TextureRegion (This is where I used this)
Create a Sprite from the TextureRegion (or two for selected/unselected, like here)
Attach the Sprite to the game's HUD
So, with code:
Convert drawable resource TextureRegion
/**
* Takes a drawable, and converts it to a TextureRegion.
* #param context
* #param resId the drawable ID
* #param textureSizeX width for our TextureRegion
* #param textureSizeY height for our TextureRegion
* #return a TextureRegion from the drawable
*/
public static TextureRegion textureRegionFromDrawable(BaseGameActivity context, int resId, int textureSizeX, int textureSizeY) {
Drawable drawable = context.getResources().getDrawable(resId);
Bitmap bitmap = drawableToBitmap(drawable);
BitmapTextureAtlasTextureSource source = new BitmapTextureAtlasTextureSource(bitmap);
BitmapTextureAtlas textureAtlas = new BitmapTextureAtlas(context.getTextureManager(), textureSizeX, textureSizeY);
textureAtlas.addTextureAtlasSource(source, 0, 0);
textureAtlas.load();
TextureRegion textureRegion = (TextureRegion) TextureRegionFactory.createFromSource(textureAtlas, source, 0, 0);
return textureRegion;
}
// Drawable to Bitmap
public static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
The utility intermediary class
public class BitmapTextureAtlasTextureSource extends BaseTextureAtlasSource implements IBitmapTextureAtlasSource {
private final int[] mColors;
public BitmapTextureAtlasTextureSource(Bitmap pBitmap) {
super(0, 0, pBitmap.getWidth(), pBitmap.getHeight());
mColors = new int[mTextureWidth * mTextureHeight];
for (int y = 0; y < mTextureHeight; ++y) {
for (int x = 0; x < mTextureWidth; ++x) {
mColors[x + y * mTextureWidth] = pBitmap.getPixel(x, y);
}
}
}
#Override
public Bitmap onLoadBitmap(Config pBitmapConfig) {
return Bitmap.createBitmap(mColors, mTextureWidth, mTextureHeight, Bitmap.Config.ARGB_8888);
}
#Override
public IBitmapTextureAtlasSource deepCopy() {
return new BitmapTextureAtlasTextureSource(Bitmap.createBitmap(mColors, mTextureWidth, mTextureHeight, Bitmap.Config.ARGB_8888));
}
#Override
public Bitmap onLoadBitmap(Config pBitmapConfig, boolean pMutable) {
// TODO Auto-generated method stub
return null;
}
}
Create two Sprite from the TextureRegions (I wanted 2 states for my button: selected/unselected), and attach them to the HUD
int textureSizeX = 512, textureSizeY = 512;
TextureRegion textureDefault = AndEngineUtils.textureRegionFromDrawable(activity, R.drawable.custom_menu_button, textureSizeX, textureSizeY);
TextureRegion textureSelected = AndEngineUtils.textureRegionFromDrawable(activity, R.drawable.custom_menu_button_selected, textureSizeX, textureSizeY);
mGameHUD = new HUD();
// 2 sprites for selected and unselected
final Sprite buttonUnselected, buttonSelected ;
buttonSelected = new Sprite(0, 0, textureSelected, activity.getVertexBufferObjectManager());
buttonSelected.setVisible(false);
buttonUnselected = new Sprite(0, 0, textureDefault, activity.getVertexBufferObjectManager()) {
#Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, float pTouchAreaLocalX, float pTouchAreaLocalY) {
switch (pSceneTouchEvent.getAction()) {
case TouchEvent.ACTION_DOWN:
// Change button
buttonSelected.setVisible(true);
this.setVisible(false);
break;
case TouchEvent.ACTION_UP:
// reset button
this.setVisible(true);
buttonSelected.setVisible(false);
break;
default:
break;
}
return super.onAreaTouched(pSceneTouchEvent, pTouchAreaLocalX, pTouchAreaLocalY);
}
};
mGameHUD.attachChild(buttonSelected);
mGameHUD.attachChild(buttonUnselected);
mGameHUD.registerTouchArea(buttonUnselected);
camera.setHUD(mGameHUD);
We can also make use of scene.setOnAreaTouchListener(touchListener) in order for an "unwanted" sliding gesture - until outside of the button - to take effet to reset the button color (see this thread).
I have just begun using AndEngine and I am not yet familiar with best practices, so feel free to correct me.
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();