Circular Image View stretched - java

I am using the following code for creating a circular image view within my project.
public class CircularImageView extends ImageView {
// Border & Selector configuration variables
private boolean hasBorder;
private boolean hasSelector;
private boolean isSelected;
private int borderWidth;
private int canvasSize;
private int selectorStrokeWidth;
// Objects used for the actual drawing
private BitmapShader shader;
private Bitmap image;
private Paint paint;
private Paint paintBorder;
private Paint paintSelectorBorder;
private ColorFilter selectorFilter;
public CircularImageView(Context context) {
this(context, null);
}
public CircularImageView(Context context, AttributeSet attrs)
{
this(context, attrs, R.attr.circularImageViewStyle);
}
public CircularImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
/**
* Initializes paint objects and sets desired attributes.
*
* #param context
* #param attrs
* #param defStyle
*/
private void init(Context context, AttributeSet attrs, int defStyle) {
// Initialize paint objects
paint = new Paint();
paint.setAntiAlias(true);
paintBorder = new Paint();
paintBorder.setAntiAlias(true);
paintSelectorBorder = new Paint();
paintSelectorBorder.setAntiAlias(true);
// load the styled attributes and set their properties
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyle, 0);
// Check if border and/or border is enabled
hasBorder = attributes.getBoolean(R.styleable.CircularImageView_border, false);
hasSelector = attributes.getBoolean(R.styleable.CircularImageView_selector, false);
// Set border properties if enabled
if(hasBorder) {
int defaultBorderSize = (int) (2 * context.getResources().getDisplayMetrics().density + 0.5f);
setBorderWidth(attributes.getDimensionPixelOffset(R.styleable.CircularImageView_border_width, defaultBorderSize));
setBorderColor(attributes.getColor(R.styleable.CircularImageView_border_color, Color.WHITE));
}
// Set selector properties if enabled
if(hasSelector) {
int defaultSelectorSize = (int) (2 * context.getResources().getDisplayMetrics().density + 0.5f);
setSelectorColor(attributes.getColor(
R.styleable.CircularImageView_selector_color, Color.TRANSPARENT));
setSelectorStrokeWidth(attributes.getDimensionPixelOffset(R.styleable.CircularImageView_selector_stroke_width, defaultSelectorSize));
setSelectorStrokeColor(attributes.getColor(R.styleable.CircularImageView_selector_stroke_color, Color.BLUE));
}
// Add shadow if enabled
if(attributes.getBoolean(R.styleable.CircularImageView_shadow, false))
addShadow();
// We no longer need our attributes TypedArray, give it back to cache
attributes.recycle();
}
/**
* Sets the CircularImageView's border width in pixels.
*
* #param borderWidth
*/
public void setBorderWidth(int borderWidth) {
this.borderWidth = borderWidth;
this.requestLayout();
this.invalidate();
}
/**
* Sets the CircularImageView's basic border color.
*
* #param borderColor
*/
public void setBorderColor(int borderColor) {
if (paintBorder != null)
paintBorder.setColor(borderColor);
this.invalidate();
}
/**
* Sets the color of the selector to be draw over the
* CircularImageView. Be sure to provide some opacity.
*
* #param selectorColor
*/
public void setSelectorColor(int selectorColor) {
this.selectorFilter = new PorterDuffColorFilter(selectorColor, PorterDuff.Mode.SRC_ATOP);
this.invalidate();
}
/**
* Sets the stroke width to be drawn around the CircularImageView
* during click events when the selector is enabled.
*
* #param selectorStrokeWidth
*/
public void setSelectorStrokeWidth(int selectorStrokeWidth) {
this.selectorStrokeWidth = selectorStrokeWidth;
this.requestLayout();
this.invalidate();
}
/**
* Sets the stroke color to be drawn around the CircularImageView
* during click events when the selector is enabled.
*
* #param selectorStrokeColor
*/
public void setSelectorStrokeColor(int selectorStrokeColor) {
if (paintSelectorBorder != null)
paintSelectorBorder.setColor(selectorStrokeColor);
this.invalidate();
}
/**
* Adds a dark shadow to this CircularImageView.
*/
public void addShadow() {
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
paintBorder.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK);
}
#Override
public void onDraw(Canvas canvas) {
// Don't draw anything without an image
if(image == null)
return;
// Nothing to draw (Empty bounds)
if(image.getHeight() == 0 || image.getWidth() == 0)
return;
// Compare canvas sizes
int oldCanvasSize = canvasSize;
canvasSize = canvas.getWidth();
if(canvas.getHeight() < canvasSize)
canvasSize = canvas.getHeight();
// Reinitialize shader, if necessary
if(oldCanvasSize != canvasSize)
refreshBitmapShader();
// Apply shader to paint
paint.setShader(shader);
// Keep track of selectorStroke/border width
int outerWidth = 0;
// Get the exact X/Y axis of the view
int center = canvasSize / 2;
if(hasSelector && isSelected) { // Draw the selector stroke & apply the selector filter, if applicable
outerWidth = selectorStrokeWidth;
center = (canvasSize - (outerWidth * 2)) / 2;
paint.setColorFilter(selectorFilter);
canvas.drawCircle(center + outerWidth, center + outerWidth, ((canvasSize - (outerWidth * 2)) / 2) + outerWidth - 4.0f, paintSelectorBorder);
}
else if(hasBorder) { // If no selector was drawn, draw a border and clear the filter instead... if enabled
outerWidth = borderWidth;
center = (canvasSize - (outerWidth * 2)) / 2;
paint.setColorFilter(null);
canvas.drawCircle(center + outerWidth, center + outerWidth, ((canvasSize - (outerWidth * 2)) / 2) + outerWidth - 4.0f, paintBorder);
}
else // Clear the color filter if no selector nor border were drawn
paint.setColorFilter(null);
// Draw the circular image itself
canvas.drawCircle(center + outerWidth, center + outerWidth, ((canvasSize - (outerWidth * 2)) / 2) - 4.0f, paint);
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
// Check for clickable state and do nothing if disabled
if(!this.isClickable()) {
this.isSelected = false;
return super.onTouchEvent(event);
}
// Set selected state based on Motion Event
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
this.isSelected = true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_SCROLL:
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_CANCEL:
this.isSelected = false;
break;
}
// Redraw image and return super type
this.invalidate();
return super.dispatchTouchEvent(event);
}
public void invalidate(Rect dirty) {
super.invalidate(dirty);
image = drawableToBitmap(getDrawable());
if(shader != null || canvasSize > 0)
refreshBitmapShader();
}
public void invalidate(int l, int t, int r, int b) {
super.invalidate(l, t, r, b);
image = drawableToBitmap(getDrawable());
if(shader != null || canvasSize > 0)
refreshBitmapShader();
}
#Override
public void invalidate() {
super.invalidate();
image = drawableToBitmap(getDrawable());
if(shader != null || canvasSize > 0)
refreshBitmapShader();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureWidth(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// The parent has determined an exact size for the child.
result = specSize;
}
else if (specMode == MeasureSpec.AT_MOST) {
// The child can be as large as it wants up to the specified size.
result = specSize;
}
else {
// The parent has not imposed any constraint on the child.
result = canvasSize;
}
return result;
}
private int measureHeight(int measureSpecHeight) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpecHeight);
int specSize = MeasureSpec.getSize(measureSpecHeight);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
// The child can be as large as it wants up to the specified size.
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = canvasSize;
}
return (result + 2);
}
/**
* Convert a drawable object into a Bitmap
*
* #param drawable
* #return
*/
public Bitmap drawableToBitmap(Drawable drawable) {
if (drawable == null) { // Don't do anything without a proper drawable
return null;
}
else if (drawable instanceof BitmapDrawable) { // Use the getBitmap() method instead if BitmapDrawable
return ((BitmapDrawable) drawable).getBitmap();
}
// Create Bitmap object out of the drawable
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
/**
* Reinitializes the shader texture used to fill in
* the Circle upon drawing.
*/
public void refreshBitmapShader() {
shader = new BitmapShader(Bitmap.createScaledBitmap(image, canvasSize, canvasSize, false), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
}
/**
* Returns whether or not this view is currently
* in its selected state.
*/
public boolean isSelected() {
return this.isSelected;
}
}
The circle effect is created, but the problem is all my pictures are being narrowed, as in the picture is too big and it's trying to fit in the circular image view. Any possible solutions to this? Thanks!

Assuming that the size of the circular image view is fixed, the only way to avoid stretching the image is to crop it to the same aspect ratio as the image view, or else introduce margins on the top or sides.
Since you are already scaling the bitmap to the canvas size inside refreshBitmapShader(), that is a convenient place to crop it using Bitmap.createBitmap():
public void refreshBitmapShader() {
int left = 0; y = 0; w = image.getWidth(), h = image.getHeight();
// decide whether we have to crop the sizes or the top and bottom:
if(w > h) // width is greater than height
{
x = (w - h) >> 1; // crop sides, half on each side
w = h;
}
else
{
y = (h - w) >> 1; // crop top and bottom
h = w;
}
Matrix m = new Matrix();
float scale = (float)canvasSize / (float)w;
m.preScale(scale, scale); // scale to canvas size
shader = new BitmapShader(Bitmap.createBitmap(image, x, y, w, h, m, false), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
}

Related

SurfaceVIew is not rendering the drawn graphics on the design view

Im making a virtual joystick controller using SurfaceView but the drawn shapes in my java code is not rendered in the design view, im very new to java with just a few months experience.
public class JoystickView extends SurfaceView
implements SurfaceHolder.Callback,
View.OnTouchListener
{
private float centerX;
private float centerY;
private float baseRadius;
private float hatRadius;
private JoystickListener joystickCallback;
private void setupDimensions()
{
centerX = (float)getWidth()/2;
centerY = (float)getHeight()/2;
baseRadius = (float)Math.min(getWidth(), getHeight())/3;
hatRadius = (float)Math.min(getWidth(), getHeight())/5;
}
public JoystickView(Context context)
{
super(context);
getHolder().addCallback(this);
setOnTouchListener(this);
if(context instanceof JoystickListener){
joystickCallback = (JoystickListener) context;}
}
public JoystickView(Context context, AttributeSet attributes, int style)
{
super(context, attributes, style);
getHolder().addCallback(this);
setOnTouchListener(this);
}
public JoystickView(Context context, AttributeSet attributes)
{
super(context, attributes);
getHolder().addCallback(this);
setOnTouchListener(this);
}
This is where the joystick circle base and top(hat) where drawn using myCanvas.drawCircle()enter image description here
private void drawJoystick(float newX, float newY)
{
if(getHolder().getSurface().isValid())
{
Canvas myCanvas = this.getHolder().lockCanvas();
Paint colors = new Paint();
myCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//These equations determine the sin and cos of the angle that the touched point is at relative to the center of the joystick
float hypotenuse = (float) Math.sqrt(Math.pow(newX - centerX, 2) + Math.pow(newY - centerY, 2));
float sin = (newY - centerY)/ hypotenuse;
float cos = (newX - centerX)/ hypotenuse;
//Drawing the base then shading
colors.setARGB(255, 50, 50, 50);
myCanvas.drawCircle(centerX, centerY, baseRadius, colors);
int ratio = 5;
for(int i = 1; i <= (int)(baseRadius/ ratio); i++)
{
colors.setARGB(150/i, 255, 0, 0); // Gradually reduce the black drawn to create an nice effect
myCanvas.drawCircle(newX- cos * hypotenuse * (ratio /baseRadius) * i,
newY - sin * hypotenuse * (ratio /baseRadius) * i, i * (hatRadius* ratio /baseRadius),colors); //gradually increase the size of the shading effect
}
//Drawing the joystick hat
for(int i = 1; i <= (int) (hatRadius / ratio); i++)
{
colors.setARGB(255, (int) (i * (255 * ratio / hatRadius)),(int)(i * (255 * ratio / hatRadius)), 255);//Change the joystick color for shading purposes
myCanvas.drawCircle(newX,newY, hatRadius - (float) i*(ratio) / 2, colors); // Drawing the shading of the hat
}
getHolder().unlockCanvasAndPost(myCanvas); //This writes the new drawing to the SurfaceView
}
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
setupDimensions();
drawJoystick(centerX, centerY);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
}
public boolean onTouch(View v, MotionEvent e)
{
if(v.equals(this)) {
if (e.getAction() != MotionEvent.ACTION_UP)
{
float displacement = (float) Math.sqrt((Math.pow(e.getX() - centerX, 2)) + Math.pow(e.getY() - centerY, 2));
if (displacement < baseRadius)
{
drawJoystick(e.getX(), e.getY());
joystickCallback.onJoystickMoved((e.getX() - centerX)/baseRadius, (e.getY() - centerY)/baseRadius, getId());
}
else {
float ratio = baseRadius / displacement;
float constrainedX = centerX + (e.getX() - centerX) * ratio;
float constrainedY = centerY + (e.getY() - centerY) * ratio;
drawJoystick(constrainedX, constrainedY);
joystickCallback.onJoystickMoved((constrainedX-centerX)/baseRadius, (constrainedY-centerY)/baseRadius, getId());
}
}
else
drawJoystick(centerX, centerY);
joystickCallback.onJoystickMoved(0,0, getId());
}
return true;
}
public interface JoystickListener
{
void onJoystickMoved(float xPercent, float yPercent, int id);
}
}

Troubles converting SVG subset to Java with java tool

I am using this tool:
https://github.com/codenameone/flamingo-svg-transcoder
you can read more about it here:
https://www.codenameone.com/blog/flamingo-svg-transcoder.html#commento-login-box-container
I used it to convert a svg file and this is the result:
/**
* This class has been automatically generated using
* Flamingo SVG transcoder.
*/
public class MyImageFromSVGSubset extends com.codename1.ui.Image implements Painter {
private int width, height;
private Transform t = Transform.makeIdentity(), t2 = Transform.makeIdentity();
public MyImageFromSVGSubset() {
super(null);
width = getOrigWidth();
height = getOrigHeight();
}
public MyImageFromSVGSubset(int width, int height) {
super(null);
this.width = width;
this.height = height;
fixAspectRatio();
}
#Override
public int getWidth() {
return width;
}
#Override
public int getHeight() {
return height;
}
#Override
public void scale(int width, int height) {
this.width = width;
this.height = height;
fixAspectRatio();
}
#Override
public MyImageFromSVGSubset scaled(int width, int height) {
MyImageFromSVGSubset f = new MyImageFromSVGSubset(width, height);
f.fixAspectRatio();
return f;
}
public Image toImage() {
Image i = Image.createImage(width, height, 0);
Graphics g = i.getGraphics();
drawImage(g, null, 0, 0, width, height);
return i;
}
private void fixAspectRatio() {
if(width == -1) {
float ar = ((float)getOrigWidth()) / ((float)getOrigHeight());
width = Math.round(((float)height) * ar);
}
if (height == -1) {
float ar = ((float)getOrigHeight()) / ((float)getOrigWidth());
height = Math.round(((float)width) * ar);
}
}
#Override
public Image fill(int width, int height) {
return new MyImageFromSVGSubset(width, height);
}
#Override
public Image applyMask(Object mask) {
return new MyImageFromSVGSubset(width, height);
}
#Override
public boolean isAnimation() {
return true;
}
#Override
public boolean requiresDrawImage() {
return true;
}
#Override
protected void drawImage(Graphics g, Object nativeGraphics, int x, int y) {
drawImage(g, nativeGraphics, x, y, width, height);
}
#Override
protected void drawImage(Graphics g, Object nativeGraphics, int x, int y, int w, int h) {
g.getTransform(t);
t2.setTransform(t);
float hRatio = ((float) w) / ((float) getOrigWidth());
float vRatio = ((float) h) / ((float) getOrigHeight());
t2.translate(x + g.getTranslateX(), y + g.getTranslateY());
t2.scale(hRatio, vRatio);
g.setTransform(t2);
paint(g);
g.setTransform(t);
}
private static void paint(Graphics g) {
int origAlpha = g.getAlpha();
Stroke baseStroke;
Shape shape;
g.setAntiAliased(true);
g.setAntiAliasedText(true);
/*Composite origComposite = g.getComposite();
if (origComposite instanceof AlphaComposite) {
AlphaComposite origAlphaComposite = (AlphaComposite)origComposite;
if (origAlphaComposite.getRule() == AlphaComposite.SRC_OVER) {
origAlpha = origAlphaComposite.getAlpha();
}
}
*/
java.util.LinkedList<Transform> transformations = new java.util.LinkedList<Transform>();
//
transformations.push(g.getTransform());
g.transform(new AffineTransform(3.7795277f, 0, 0, 3.7795277f, 0, 0).toTransform());
// _0
// _0_0
// _0_0_0
shape = new Rectangle2D(0.008569182828068733, 0.0054579987190663815, 6.3905863761901855, 6.390747547149658);
g.setColor(0xffffff);
g.fillShape(shape);
// _0_0_1
shape = new GeneralPath();
((GeneralPath) shape).moveTo(5.0011573, 1.454892);
((GeneralPath) shape).curveTo(4.914109, 1.454892, 4.8439946, 1.526065, 4.8439946, 1.6128483);
...
...
//very long sequence of graphical instructions that corresponds to the actual drawing
...
...
((GeneralPath) shape).closePath();
g.fillShape(shape);
g.setTransform(transformations.pop()); // _0
g.setAlpha(origAlpha);
}
/**
* Returns the X of the bounding box of the original SVG image.
*
* #return The X of the bounding box of the original SVG image.
*/
public static int getOrigX() {
return 1;
}
/**
* Returns the Y of the bounding box of the original SVG image.
*
* #return The Y of the bounding box of the original SVG image.
*/
public static int getOrigY() {
return 1;
}
/**
* Returns the width of the bounding box of the original SVG image.
*
* #return The width of the bounding box of the original SVG image.
*/
public static int getOrigWidth() {
return 24;
}
/**
* Returns the height of the bounding box of the original SVG image.
*
* #return The height of the bounding box of the original SVG image.
*/
public static int getOrigHeight() {
return 24;
}
#Override
public void paint(Graphics g, Rectangle rect) {
drawImage(g, null, rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
}
}
It's Java code to be used in my application (Codename One) but it doesn't work for me.
I put the following code into a working layout:
button.setIcon(new MyImageFromSVGSubset(256,256));
The image is present because it occupies the right space (256x256) but nothing is drawn.
What is the mistake?

Custom view being drawn sometimes

I have a custom view, that is sometimes being drawn and sometimes not .. this is reproduce-able across multiple phone.
Don't know why this is happening .. setDimensions() is being called during the onMeasure call of a GalleryGridElement (relative layout) which I use as gallery elements in my recyclerview.
One example would be .. going into the recycler view gallery activity, the circular progress view is there .. when you leave the activity and come back .. onResume creates a new adapter and gives it to the recycler view .. however the circular progress views don't show this time:
public class CircularProgressView extends View {
private Paint mIndicatorColour;
private RectF mIndicatorRect;
private Paint mBackCircleColour;
private static final float START_ANGLE = -90;
private volatile float mStopAngle = 0;
private float mOutterCircleStrokeWidth = 20;
private float mInnerCircleStrokeWidth = 16;
private float mViewWidth = 0, mViewHeight = 0;
private volatile int mCurrentProgress = 0;
private ExecutorService mExecutorService;
public CircularProgressView(Context context) {
super(context);
setUp();
}
public CircularProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
setUp();
}
public CircularProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setUp();
}
private void setUp(){
mIndicatorRect = new RectF(0,0,300,300);
mIndicatorColour = new Paint();
mIndicatorColour.setColor(Color.parseColor("#D62F85"));
mIndicatorColour.setStyle(Paint.Style.STROKE);
mIndicatorColour.setStrokeWidth(mInnerCircleStrokeWidth);
mIndicatorColour.setAntiAlias(true);
mIndicatorColour.setDither(true);
mIndicatorColour.setStrokeJoin(Paint.Join.ROUND);
mIndicatorColour.setStrokeCap(Paint.Cap.ROUND);
mBackCircleColour = new Paint();
mBackCircleColour.setColor(Color.WHITE);
mBackCircleColour.setStyle(Paint.Style.STROKE);
mBackCircleColour.setStrokeWidth(mOutterCircleStrokeWidth);
mBackCircleColour.setAntiAlias(true);
mBackCircleColour.setDither(true);
mBackCircleColour.setStrokeJoin(Paint.Join.ROUND);
mBackCircleColour.setStrokeCap(Paint.Cap.ROUND);
mExecutorService = Executors.newSingleThreadExecutor();
}
public void setDimensions(float width, int scaleCircleThicknessValue){
mViewHeight = width;
mViewWidth = width;
mIndicatorRect.left = mIndicatorRect.top = mOutterCircleStrokeWidth;
mIndicatorRect.right = mIndicatorRect.bottom = width - mOutterCircleStrokeWidth;
mIndicatorColour.setStrokeWidth(mInnerCircleStrokeWidth);
mBackCircleColour.setStrokeWidth(mOutterCircleStrokeWidth);
mInnerCircleStrokeWidth = (0.1f * scaleCircleThicknessValue) * width;
mOutterCircleStrokeWidth = mInnerCircleStrokeWidth + 5;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int desiredWidth = Math.round(mViewWidth);
int desiredHeight = Math.round(mViewHeight);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
//Set values
setMeasuredDimension(width, height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawArc(mIndicatorRect, 0, 360, false, mBackCircleColour);
canvas.drawArc(mIndicatorRect, START_ANGLE, mStopAngle, false, mIndicatorColour);
}
public synchronized void setProgress(final int progress) {
if ((mCurrentProgress != progress) && (progress > 0)) {
mCurrentProgress = progress;
mExecutorService.submit(new Runnable() {
#Override
public void run() {
final float currentAngle = mStopAngle;
float newAngle = (360f * ((float) progress / 100f));
float step = (Math.round(newAngle) - Math.round(currentAngle)) <= 1 ? 1 : (newAngle - currentAngle)/5f;
if (step < 0.01) {
newAngle = 359;
}
for (float i = currentAngle; i < newAngle; i += step) {
try {
mStopAngle = i;
postInvalidate();
Thread.sleep(1000 / 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
}
public float getProgress(){
return (360f-mStopAngle) < 1f ? 1 : mStopAngle / 360f;
}
}
Just following up on this incase someone runs into this issue:
Since I am using a custom view inside a custom view with the recycler view.
When I bind the view-holder to model, I make sure to completely re-render the view ... ie :
mCircularProgressView.setVisibility(VISIBLE);
mCircularProgressView.setDimensions(getWidth() * 0.82f, 1);
mCircularProgressView.requestLayout();
mCircularProgressView.postInvalidate();
This makes sure the view is drawn no matter what.

Why is my TextView text size changing when I scroll RecycleView? (using AutoResizeTextView)

I use a class to resize my TextViews in a list (RecycleView) text to fit the width. Initially the TextViews in all of the list adjust perfectly. However when I scroll back up it seems like the text sizes are getting mixed up with other rows. I know it is something to do with how it reuses in the RecycleView but is there any way fix this? I don't want TextView font size changing after initial load. It is static data.
Here is my Adapter...
public class EventsAdapter extends RecyclerView.Adapter<EventsAdapter.EventHolder> {
private Event[] eventslistData;
private LayoutInflater layoutInflater;
public EventsAdapter(Event[] eventsListData, Context c) {
this.layoutInflater = LayoutInflater.from(c);
this.eventslistData = eventsListData;
}
#Override
public EventHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = layoutInflater.inflate(R.layout.event_list_item, parent, false);
return new EventHolder(view);
}
#Override
public void onBindViewHolder(EventHolder holder, int position) {
Event event = eventslistData[position];
holder.eventNameTextView.setText(event.getEventName());
holder.eventLocationTextView.setText(event.getEventLocation());
holder.eventDateTextView.setText(event.getEventDate());
}
#Override
public int getItemCount() {
return eventslistData.length;
}
class EventHolder extends RecyclerView.ViewHolder {
private TextView eventNameTextView;
private TextView eventLocationTextView;
private TextView eventDateTextView;
public EventHolder(View itemView) {
super(itemView);
eventNameTextView = (TextView)itemView.findViewById(R.id.eventNameTextView);
eventLocationTextView = (TextView)itemView.findViewById(R.id.eventLocationTextView);
eventDateTextView = (TextView)itemView.findViewById(R.id.eventDateTextView);
}
}
}
Just in case here is all of my AutoResizeTextView class...
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 12;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
* #param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
#Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
* #param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
* #return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
* #param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
* #return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add ellipsis to text that overflows at the smallest text size
* #param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add ellipsis to text that overflows at the smallest text size
* #return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if (mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
mMaxTextSize = mTextSize;
}
}
/**
* Resize text after measuring
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
* #param width
* #param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
if (getTransformationMethod() != null) {
text = getTransformationMethod().getTransformation(text, this);
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while (textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an ellipsis
if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint paint = new TextPaint(textPaint);
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while (width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if (mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
// modified: make a copy of the original TextPaint object for measuring
// (apparently the object gets modified while measuring, see also the
// docs for TextView.getPaint() (which states to access it read-only)
TextPaint paintCopy = new TextPaint(paint);
// Update the text paint object
paintCopy.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
It was a simple fix. All I had to do was override getItemViewType in EventsAdapter.
#Override
public int getItemViewType(int position){
return position;
}

Make movement more smooth

I'm trying to make this game app.
I basically have this BitMap bMapEgg moving down every time UpdateCourt() gets called in a Surfaceview ChickenView. The problem I'm facing is that it looks really chunky. The movement of the bitmap doesn't look smooth.
ballPosition.y += 18; is the code that makes it move.
Any ideas how to make it look smooth?
public class MainActivity extends Activity implements View.OnTouchListener{
static Canvas canvas;
static ChickenView chickenView;
//Used for getting display details like the number of pixels
static Display display;
static Point size;
static int screenWidth;
static int screenHeight;
static Point ballPosition;
static int ballWidth;
static boolean ballIsMovingDown;
//stats
static long lastFrameTime;
static int fps;
static int score;
static int lives;
static float bitmapPositionX ;
static float bitmapPositionY ;
static float bitmapWidth ;
static float bitmapHeight;
static Bitmap bMapEgg;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
chickenView = (ChickenView) findViewById(R.id.chickenView);
//Get the screen size in pixels
display = getWindowManager().getDefaultDisplay();
size = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
display.getSize(size);
}
screenWidth = size.x;
screenHeight = size.y;
ballWidth = screenWidth / 35;
ballPosition = new Point();
ballPosition.x = screenWidth / 2;
ballPosition.y = 1 + ballWidth;
final ImageView imageEggs = (ImageView)findViewById(R.id.imageEggs);
final ImageView imageUpgrade = (ImageView)findViewById(R.id.imageUpgrade);
TextView test = (TextView)findViewById(R.id.textEggs);
imageUpgrade.setOnTouchListener(this);
chickenView.setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (v.getId()) {
case R.id.imageUpgrade:
if (event.getAction() == MotionEvent.ACTION_DOWN) {
final int x = (int) event.getX();
final int y = (int) event.getY();
//now map the coords we got to the
//bitmap (because of scaling)
ImageView imageView = ((ImageView)v);
Bitmap bitmap =((BitmapDrawable)imageView.getDrawable()).getBitmap();
int pixel = bitmap.getPixel(x,y);
//now check alpha for transparency
int alpha = Color.alpha(pixel);
if (alpha != 0) {
//do whatever you would have done for your click event here
Intent i;
i = new Intent(this, StructureActivity.class);
startActivity(i);
}
}
break;
case R.id.chickenView:
float x = event.getX();
float y = event.getY();
// Replace these with the correct values (bitmap x, y, width & height)
float x1 = bitmapPositionX;
float x2 = x1 + bitmapWidth;
float y1 = bitmapPositionY;
float y2 = y1 + bitmapHeight;
// Test to see if touch is inside the bitmap
if (x > x1 && x < x2 && y > y1 && y < y2) {
// Bitmap was touched
Log.v("test", "test");
} else {
Log.v("werkt ghil ni", "test");
}
break;
}
return true;
}
static class ChickenView extends SurfaceView implements Runnable {
Thread ourThread = null;
SurfaceHolder ourHolder;
volatile boolean playingSquash;
Paint paint;
public ChickenView(Context context) {
super(context);
init(context);
}
public ChickenView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ChickenView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
ourHolder = getHolder();
paint = new Paint();
ballIsMovingDown = true;
}
#Override
public void run() {
while (playingSquash) {
updateCourt();
drawCourt();
controlFPS();
}
}
public void updateCourt() {
//depending upon the two directions we should be
//moving in adjust our x any positions
if (ballIsMovingDown) {
ballPosition.y += 18;
}
//if hits bottom
if (ballPosition.y > screenHeight - 7*ballWidth) {
ballIsMovingDown = false;
}
}
public void drawCourt() {
if (ourHolder.getSurface().isValid()) {
canvas = ourHolder.lockCanvas();
//Paint paint = new Paint();
canvas.drawColor(Color.BLACK);//the background
paint.setColor(Color.argb(255, 255, 255, 255));
paint.setTextSize(45);
canvas.drawText("Score:" + score + " Lives:" + lives + " fps:" + fps, 20, 40, paint);
Bitmap bMapEgg = BitmapFactory.decodeResource(getResources(), R.drawable.egg);
bMapEgg = scaleDown(bMapEgg,180, true);
Bitmap bMapBackground = BitmapFactory.decodeResource(getResources(), R.drawable.backgrounddd);
canvas.drawBitmap(bMapBackground, 0, 0, paint);
canvas.drawBitmap(bMapEgg, ballPosition.x, ballPosition.y, paint);
bitmapPositionX = ballPosition.x;
bitmapPositionY = ballPosition.y;
bitmapWidth = bMapEgg.getWidth();
bitmapHeight = bMapEgg.getHeight();
ourHolder.unlockCanvasAndPost(canvas);
}
}
public void controlFPS() {
long timeThisFrame = (System.currentTimeMillis() - lastFrameTime);
long timeToSleep = 15 - timeThisFrame;
if (timeThisFrame > 0) {
fps = (int) (1000 / timeThisFrame);
}
if (timeToSleep > 0) {
try {
ourThread.sleep(timeToSleep);
} catch (InterruptedException e) {
}
}
lastFrameTime = System.currentTimeMillis();
}
public void pause() {
playingSquash = false;
try {
ourThread.join();
} catch (InterruptedException e) {
}
}
public void resume() {
playingSquash = true;
ourThread = new Thread(this);
ourThread.start();
}
}
#Override
protected void onStop() {
super.onStop();
while (true) {
chickenView.pause();
break;
}
finish();
}
#Override
protected void onResume() {
super.onResume();
chickenView.resume();
}
#Override
protected void onPause() {
super.onPause();
chickenView.pause();
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
chickenView.pause();
finish();
return true;
}
return false;
}
public static Bitmap scaleDown(Bitmap realImage, float maxImageSize,
boolean filter) {
float ratio = Math.min(
(float) maxImageSize / realImage.getWidth(),
(float) maxImageSize / realImage.getHeight());
int width = Math.round((float) ratio * realImage.getWidth());
int height = Math.round((float) ratio * realImage.getHeight());
Bitmap newBitmap = Bitmap.createScaledBitmap(realImage, width,
height, filter);
return newBitmap;
}
}
The problem is that in each frame you decode and scale all bitmaps all over again.
Move your bitmaps decoding and scaling out of the draw method, put it in an initialization method and store decoded and scaled bitmaps in member variables (maybe even static ones) so you can just reuse them for drawing in each frame.

Categories