Android Custom View Edge Clipping with ripple animation - android

I'm using a custom view to get ripple effect for the pre-lollipop devices. But I also need to customize the container shape like a curved shape.I want to be the button like this.
As you can see in the second and third buttons when we tap the view the ripple effect animation goes outside of the container view. So how to resolve this?
Please note that I want this ripple effect for the Kitkat version with the ability to change the ripple color. So is this possible?
Here is my custom view which is used for the ripple effect
public class MyRippleView extends FrameLayout {
private int WIDTH;
private int HEIGHT;
private int frameRate = 10;
private int rippleDuration = 400;
private int rippleAlpha = 90;
private Handler canvasHandler;
private float radiusMax = 0;
private boolean animationRunning = false;
private int timer = 0;
private int timerEmpty = 0;
private int durationEmpty = -1;
private float x = -1;
private float y = -1;
private int zoomDuration;
private float zoomScale;
private ScaleAnimation scaleAnimation;
private Boolean hasToZoom;
private Boolean isCentered;
private Integer rippleType;
private Paint paint;
private Bitmap originBitmap;
private int rippleColor;
private int ripplePadding;
private GestureDetector gestureDetector;
private final Runnable runnable = new Runnable() {
#Override
public void run() {
invalidate();
}
};
private OnRippleCompleteListener onCompletionListener;
public MyRippleView(Context context) {
super(context);
}
public MyRippleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MyRippleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
/**
* Method that initializes all fields and sets listeners
*
* #param context Context used to create this view
* #param attrs Attribute used to initialize fields
*/
private void init(final Context context, final AttributeSet attrs) {
if (isInEditMode())
return;
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleView);
rippleColor = typedArray.getColor(R.styleable.RippleView_rv_color, getResources().getColor(R.color.rippelColor));
rippleType = typedArray.getInt(R.styleable.RippleView_rv_type, 0);
hasToZoom = typedArray.getBoolean(R.styleable.RippleView_rv_zoom, false);
isCentered = typedArray.getBoolean(R.styleable.RippleView_rv_centered, false);
rippleDuration = typedArray.getInteger(R.styleable.RippleView_rv_rippleDuration, rippleDuration);
frameRate = typedArray.getInteger(R.styleable.RippleView_rv_framerate, frameRate);
rippleAlpha = typedArray.getInteger(R.styleable.RippleView_rv_alpha, rippleAlpha);
ripplePadding = typedArray.getDimensionPixelSize(R.styleable.RippleView_rv_ripplePadding, 0);
canvasHandler = new Handler();
zoomScale = typedArray.getFloat(R.styleable.RippleView_rv_zoomScale, 1.03f);
zoomDuration = typedArray.getInt(R.styleable.RippleView_rv_zoomDuration, 200);
typedArray.recycle();
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(rippleColor);
paint.setAlpha(rippleAlpha);
this.setWillNotDraw(false);
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public void onLongPress(MotionEvent event) {
super.onLongPress(event);
animateRipple(event);
sendClickEvent(true);
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return true;
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
});
this.setDrawingCacheEnabled(true);
this.setClickable(true);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (animationRunning) {
if (rippleDuration <= timer * frameRate) {
animationRunning = false;
timer = 0;
durationEmpty = -1;
timerEmpty = 0;
canvas.restore();
invalidate();
if (onCompletionListener != null) onCompletionListener.onComplete(this);
return;
} else
canvasHandler.postDelayed(runnable, frameRate);
if (timer == 0)
canvas.save();
canvas.drawCircle(x, y, (radiusMax * (((float) timer * frameRate) / rippleDuration)), paint);
paint.setColor(Color.parseColor("#ffff4444"));
if (rippleType == 1 && originBitmap != null && (((float) timer * frameRate) / rippleDuration) > 0.4f) {
if (durationEmpty == -1)
durationEmpty = rippleDuration - timer * frameRate;
timerEmpty++;
final Bitmap tmpBitmap = getCircleBitmap((int) ((radiusMax) * (((float) timerEmpty * frameRate) / (durationEmpty))));
canvas.drawBitmap(tmpBitmap, 0, 0, paint);
tmpBitmap.recycle();
}
paint.setColor(rippleColor);
if (rippleType == 1) {
if ((((float) timer * frameRate) / rippleDuration) > 0.6f)
paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timerEmpty * frameRate) / (durationEmpty)))));
else
paint.setAlpha(rippleAlpha);
}
else
paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timer * frameRate) / rippleDuration))));
timer++;
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
WIDTH = w;
HEIGHT = h;
scaleAnimation = new ScaleAnimation(1.0f, zoomScale, 1.0f, zoomScale, w / 2, h / 2);
scaleAnimation.setDuration(zoomDuration);
scaleAnimation.setRepeatMode(Animation.REVERSE);
scaleAnimation.setRepeatCount(1);
}
/**
* Launch Ripple animation for the current view with a MotionEvent
*
* #param event MotionEvent registered by the Ripple gesture listener
*/
public void animateRipple(MotionEvent event) {
createAnimation(event.getX(), event.getY());
}
/**
* Launch Ripple animation for the current view centered at x and y position
*
* #param x Horizontal position of the ripple center
* #param y Vertical position of the ripple center
*/
public void animateRipple(final float x, final float y) {
createAnimation(x, y);
}
/**
* Create Ripple animation centered at x, y
*
* #param x Horizontal position of the ripple center
* #param y Vertical position of the ripple center
*/
private void createAnimation(final float x, final float y) {
if (this.isEnabled() && !animationRunning) {
if (hasToZoom)
this.startAnimation(scaleAnimation);
radiusMax = Math.max(WIDTH, HEIGHT);
if (rippleType != 2)
radiusMax /= 2;
radiusMax -= ripplePadding;
if (isCentered || rippleType == 1) {
this.x = getMeasuredWidth() / 2;
this.y = getMeasuredHeight() / 2;
} else {
this.x = x;
this.y = y;
}
animationRunning = true;
if (rippleType == 1 && originBitmap == null)
originBitmap = getDrawingCache(true);
invalidate();
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
animateRipple(event);
sendClickEvent(false);
}
return super.onTouchEvent(event);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
this.onTouchEvent(event);
return super.onInterceptTouchEvent(event);
}
/**
* Send a click event if parent view is a Listview instance
*
* #param isLongClick Is the event a long click ?
*/
private void sendClickEvent(final Boolean isLongClick) {
if (getParent() instanceof AdapterView) {
final AdapterView adapterView = (AdapterView) getParent();
final int position = adapterView.getPositionForView(this);
final long id = adapterView.getItemIdAtPosition(position);
if (isLongClick) {
if (adapterView.getOnItemLongClickListener() != null)
adapterView.getOnItemLongClickListener().onItemLongClick(adapterView, this, position, id);
} else {
if (adapterView.getOnItemClickListener() != null)
adapterView.getOnItemClickListener().onItemClick(adapterView, this, position, id);
}
}
}
private Bitmap getCircleBitmap(final int radius) {
final Bitmap output = Bitmap.createBitmap(originBitmap.getWidth(), originBitmap.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(output);
final Paint paint = new Paint();
final Rect rect = new Rect((int)(x - radius), (int)(y - radius), (int)(x + radius), (int)(y + radius));
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
canvas.drawCircle(x, y, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(originBitmap, rect, rect, paint);
return output;
}
/**
* Set Ripple color, default is #FFFFFF
*
* #param rippleColor New color resource
*/
#ColorRes
public void setRippleColor(int rippleColor) {
this.rippleColor = getResources().getColor(rippleColor);
}
public int getRippleColor() {
return rippleColor;
}
public RippleType getRippleType()
{
return RippleType.values()[rippleType];
}
/**
* Set Ripple type, default is RippleType.SIMPLE
*
* #param rippleType New Ripple type for next animation
*/
public void setRippleType(final RippleType rippleType)
{
this.rippleType = rippleType.ordinal();
}
public Boolean isCentered()
{
return isCentered;
}
/**
* Set if ripple animation has to be centered in its parent view or not, default is False
*
* #param isCentered
*/
public void setCentered(final Boolean isCentered)
{
this.isCentered = isCentered;
}
public int getRipplePadding()
{
return ripplePadding;
}
/**
* Set Ripple padding if you want to avoid some graphic glitch
*
* #param ripplePadding New Ripple padding in pixel, default is 0px
*/
public void setRipplePadding(int ripplePadding)
{
this.ripplePadding = ripplePadding;
}
public Boolean isZooming()
{
return hasToZoom;
}
/**
* At the end of Ripple effect, the child views has to zoom
*
* #param hasToZoom Do the child views have to zoom ? default is False
*/
public void setZooming(Boolean hasToZoom)
{
this.hasToZoom = hasToZoom;
}
public float getZoomScale()
{
return zoomScale;
}
/**
* Scale of the end animation
*
* #param zoomScale Value of scale animation, default is 1.03f
*/
public void setZoomScale(float zoomScale)
{
this.zoomScale = zoomScale;
}
public int getZoomDuration()
{
return zoomDuration;
}
/**
* Duration of the ending animation in ms
*
* #param zoomDuration Duration, default is 200ms
*/
public void setZoomDuration(int zoomDuration)
{
this.zoomDuration = zoomDuration;
}
public int getRippleDuration()
{
return rippleDuration;
}
/**
* Duration of the Ripple animation in ms
*
* #param rippleDuration Duration, default is 400ms
*/
public void setRippleDuration(int rippleDuration)
{
this.rippleDuration = rippleDuration;
}
public int getFrameRate()
{
return frameRate;
}
/**
* Set framerate for Ripple animation
*
* #param frameRate New framerate value, default is 10
*/
public void setFrameRate(int frameRate)
{
this.frameRate = frameRate;
}
public int getRippleAlpha()
{
return rippleAlpha;
}
/**
* Set alpha for ripple effect color
*
* #param rippleAlpha Alpha value between 0 and 255, default is 90
*/
public void setRippleAlpha(int rippleAlpha)
{
this.rippleAlpha = rippleAlpha;
}
public void setOnRippleCompleteListener(OnRippleCompleteListener listener) {
this.onCompletionListener = listener;
}
/**
* Defines a callback called at the end of the Ripple effect
*/
public interface OnRippleCompleteListener {
void onComplete(MyRippleView rippleView);
}
public enum RippleType {
SIMPLE(0),
DOUBLE(1),
RECTANGLE(2);
int type;
RippleType(int type)
{
this.type = type;
}
}
}
In the layout XML file
<FrameLayout
android:background="#drawable/curved_button"
android:layout_width="match_parent"
android:layout_height="50dp">
<com.package.MyRippleView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:rv_color="#color/colorAccent"
rv_centered="true">
</com.package.MyRippleView>
</FrameLayout>
Curved Shape
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item >
<shape android:shape="rectangle" >
<corners android:radius="40dip" />
<stroke android:width="1dp" android:color="#FF9A00" />
</shape>
</item>

It's possible. The easiest way is to use Carbon which does such things just like that. I was able to recreate your button using only xml and run it on Gingerbread.
<carbon.widget.Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rounded with ripple"
android:textColor="#color/carbon_amber_700"
app:carbon_cornerRadius="100dp"
app:carbon_backgroundTint="#color/carbon_white"
app:carbon_rippleColor="#40ff0000"
app:carbon_stroke="#color/carbon_amber_700"
app:carbon_strokeWidth="2dp" />
The downside is that Carbon is huge and you may not want to include it just for that one button.
If you wish to do that by yourself, you should use a path and a PorterDuff mode to clip your button to a rounded rect.
private float cornerRadius;
private Path cornersMask;
private static PorterDuffXfermode pdMode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
private void initCorners() {
cornersMask = new Path();
cornersMask.addRoundRect(new RectF(0, 0, getWidth(), getHeight()), cornerRadius, cornerRadius, Path.Direction.CW);
cornersMask.setFillType(Path.FillType.INVERSE_WINDING);
}
#Override
public void draw(#NonNull Canvas canvas) {
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
super.draw(canvas);
paint.setXfermode(pdMode);
canvas.drawPath(cornersMask, paint);
canvas.restoreToCount(saveCount);
paint.setXfermode(null);
}
And you should probably use ViewOutlineProvider on Lollipop to use native stuff where possible.

Related

Indeterminate ProgressBar with round edges

I am simply trying to customize the default indeterminate progressbar. Please how do I go from this
<ProgressBar
android:layout_width="100dp"
android:layout_height="100dp"
android:indeterminate="true"
android:indeterminateTintMode="src_atop"
android:indeterminateTint="#FFFFFF"/>
asper:
to this (easily):
Update: seems the question isn't a bit straight forward so I have added .gifs below to further explain, I want this (with sharp edges): to simply become this (with round edges):
sorry about the color change, that was the best google turned up
You can use https://github.com/korre/android-circular-progress-bar
There you have a method useRoundedCorners you need to pass false to make it not round by-default it is round at the edge
There is a custom class you can actually take from that library(It is more than enough),
public class CircularProgressBar extends View {
private int mViewWidth;
private int mViewHeight;
private final float mStartAngle = -90; // Always start from top (default is: "3 o'clock on a watch.")
private float mSweepAngle = 0; // How long to sweep from mStartAngle
private float mMaxSweepAngle = 360; // Max degrees to sweep = full circle
private int mStrokeWidth = 20; // Width of outline
private int mAnimationDuration = 400; // Animation duration for progress change
private int mMaxProgress = 100; // Max progress to use
private boolean mDrawText = true; // Set to true if progress text should be drawn
private boolean mRoundedCorners = true; // Set to true if rounded corners should be applied to outline ends
private int mProgressColor = Color.BLACK; // Outline color
private int mTextColor = Color.BLACK; // Progress text color
private final Paint mPaint; // Allocate paint outside onDraw to avoid unnecessary object creation
public CircularProgressBar(Context context) {
this(context, null);
}
public CircularProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initMeasurments();
drawOutlineArc(canvas);
if (mDrawText) {
drawText(canvas);
}
}
private void initMeasurments() {
mViewWidth = getWidth();
mViewHeight = getHeight();
}
private void drawOutlineArc(Canvas canvas) {
final int diameter = Math.min(mViewWidth, mViewHeight) - (mStrokeWidth * 2);
final RectF outerOval = new RectF(mStrokeWidth, mStrokeWidth, diameter, diameter);
mPaint.setColor(mProgressColor);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(mRoundedCorners ? Paint.Cap.ROUND : Paint.Cap.BUTT);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawArc(outerOval, mStartAngle, mSweepAngle, false, mPaint);
}
private void drawText(Canvas canvas) {
mPaint.setTextSize(Math.min(mViewWidth, mViewHeight) / 5f);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setStrokeWidth(0);
mPaint.setColor(mTextColor);
// Center text
int xPos = (canvas.getWidth() / 2);
int yPos = (int) ((canvas.getHeight() / 2) - ((mPaint.descent() + mPaint.ascent()) / 2)) ;
canvas.drawText(calcProgressFromSweepAngle(mSweepAngle) + "%", xPos, yPos, mPaint);
}
private float calcSweepAngleFromProgress(int progress) {
return (mMaxSweepAngle / mMaxProgress) * progress;
}
private int calcProgressFromSweepAngle(float sweepAngle) {
return (int) ((sweepAngle * mMaxProgress) / mMaxSweepAngle);
}
/**
* Set progress of the circular progress bar.
* #param progress progress between 0 and 100.
*/
public void setProgress(int progress) {
ValueAnimator animator = ValueAnimator.ofFloat(mSweepAngle, calcSweepAngleFromProgress(progress));
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(mAnimationDuration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mSweepAngle = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.start();
}
public void setProgressColor(int color) {
mProgressColor = color;
invalidate();
}
public void setProgressWidth(int width) {
mStrokeWidth = width;
invalidate();
}
public void setTextColor(int color) {
mTextColor = color;
invalidate();
}
public void showProgressText(boolean show) {
mDrawText = show;
invalidate();
}
/**
* Toggle this if you don't want rounded corners on progress bar.
* Default is true.
* #param roundedCorners true if you want rounded corners of false otherwise.
*/
public void useRoundedCorners(boolean roundedCorners) {
mRoundedCorners = roundedCorners;
invalidate();
}}
Then you can set the view in your xml like
<yourPackageName.CircularProgressBar
android:id="#+id/circularProgress"
android:layout_width="180dp"
android:layout_height="180dp"/>
And in your class you can call it like this,
CircularProgressBar circularProgressBar = (CircularProgressBar)
findViewById(R.id.circularProgress);
circularProgressBar.setProgress(50);
circularProgressBar.setProgressColor(Color.BLUE);

How to create Circle ProgressDrawable to use in fresco?

I'm new to Fresco library and trying to show circle Progressbar when loading image from uri into DraweeView. Now I'm using default progressbar as below:
GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(getResources());
GenericDraweeHierarchy hierarchy = builder.setFadeDuration(fadeInTime).build();
hierarchy.setProgressBarImage(new ProgressBarDrawable());
thumbnailImageView.setHierarchy(hierarchy);
however, the ProgressBar is horizontal ProgressBar? Is there anyway to change it to circle?
After a lot of time to searching with Google, but I cant find any result. I decided to write my own CircleProgressDrawable. I just wanna share it for anyone who dont want to waste time as me.
public class CircleProgressDrawable extends ProgressBarDrawable {
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mBackgroundColor = 0x80000000;
private int mColor = 0x800080FF;
private int mBarWidth = 20;
private int mLevel = 0;
private boolean mHideWhenZero = false;
private int radius = 50;
public CircleProgressDrawable() {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10f);
}
public void setRadius(int radius) {
this.radius = radius;
}
/**
* Sets the progress bar color.
*/
public void setColor(int color) {
if (mColor != color) {
mColor = color;
invalidateSelf();
}
}
/**
* Gets the progress bar color.
*/
public int getColor() {
return mColor;
}
/**
* Sets the progress bar background color.
*/
public void setBackgroundColor(int backgroundColor) {
if (mBackgroundColor != backgroundColor) {
mBackgroundColor = backgroundColor;
invalidateSelf();
}
}
/**
* Gets the progress bar background color.
*/
public int getBackgroundColor() {
return mBackgroundColor;
}
/**
* Sets the progress bar width.
*/
public void setBarWidth(int barWidth) {
if (mBarWidth != barWidth) {
mBarWidth = barWidth;
invalidateSelf();
}
}
/**
* Gets the progress bar width.
*/
public int getBarWidth() {
return mBarWidth;
}
/**
* Sets whether the progress bar should be hidden when the progress is 0.
*/
public void setHideWhenZero(boolean hideWhenZero) {
mHideWhenZero = hideWhenZero;
}
/**
* Gets whether the progress bar should be hidden when the progress is 0.
*/
public boolean getHideWhenZero() {
return mHideWhenZero;
}
#Override
protected boolean onLevelChange(int level) {
mLevel = level;
invalidateSelf();
return true;
}
#Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
#Override
public int getOpacity() {
return DrawableUtils.getOpacityFromColor(mPaint.getColor());
}
#Override
public void draw(Canvas canvas) {
if (mHideWhenZero && mLevel == 0) {
return;
}
drawCircle(canvas, mBackgroundColor);
drawArc(canvas, mLevel, mColor);
}
private final int MAX_LEVEL = 10000;
private void drawArc(Canvas canvas, int level, int color) {
mPaint.setColor(color);
Rect bounds = getBounds();
// find center point
int xpos = bounds.left + bounds.width() / 2;
int ypos = bounds.bottom - bounds.height() / 2;
RectF rectF = new RectF(xpos - radius, ypos - radius, xpos + radius, ypos + radius);
float degree = (float) level / (float) MAX_LEVEL * 360;
canvas.drawArc(rectF, 270, degree, false, mPaint);
LogUtils.e("level: " + level + ", degree: " + degree);
}
private void drawCircle(Canvas canvas, int color) {
mPaint.setColor(color);
Rect bounds = getBounds();
int xpos = bounds.left + bounds.width() / 2;
int ypos = bounds.bottom - bounds.height() / 2;
canvas.drawCircle(xpos, ypos, radius, mPaint);
}
}

How to add text to floating action button?

I want to display text on a floating action button instead of a image.I tried using the android:contentDescription="string" but action button appears to be empty do i need to set some color or do anything else.Please let me know.
In our project we are using a custom class called TextDrawable. This class extends Drawable and in its method draw(Canvas) it simple draws the text on canvas. The class is suitable for our specific needs but I think, the idea (mainly in draw() method will help you:
public class TextDrawable extends Drawable {
protected final Paint textPaint;
protected ColorStateList color;
protected String text;
protected int iHeight;
protected int iWidth;
protected int measuredWidth, measuredHeight;
private float ascent;
/**
* A flag whether the drawable is stateful - whether to redraw if the state of view has changed
*/
protected boolean stateful;
/**
* Vertical alignment of text
*/
private VerticalAlignment verticalAlignment;
.... some constructors...
public TextDrawable(Context ctx, String text, ColorStateList color, float textSize, VerticalAlignment verticalAlignment) {
textPaint = new Paint();
this.text = text;
initPaint();
this.textPaint.setTextSize(textSize);
measureSize();
setBounds(0, 0, iWidth, iHeight);
this.color = color;
textPaint.setColor(color.getDefaultColor());
this.verticalAlignment = verticalAlignment;
}
/**
* Set bounds of drawable to start on coordinate [0,0] and end on coordinate[measuredWidth,
* measuredHeight]
*/
public final void setBoundsByMeasuredSize() {
setBounds(0, 0, measuredWidth, measuredHeight);
invalidateSelf();
}
#Override
public boolean isStateful() {
return stateful;
}
public void setStateful(boolean stateful) {
this.stateful = stateful;
}
private void initPaint() {
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
}
/**
* Vertical alignment of text within the drawable (Horizontally it is always aligned to center
*/
public VerticalAlignment getVerticalAlignment() {
return verticalAlignment;
}
/**
* Vertical alignment of text within the drawable (Horizontally it is always aligned to center
*/
public void setVerticalAlignment(VerticalAlignment verticalAlignment) {
if (this.verticalAlignment != verticalAlignment) {
this.verticalAlignment = verticalAlignment;
invalidateSelf();
}
}
/**
* Displayed text
*/
public String getText() {
return text;
}
/**
* Displayed text
*/
public void setText(String text) {
if (this.text == null || !this.text.equals(text)) {
this.text = text;
invalidateSelf();
}
}
/**
* The color of text
*/
public ColorStateList getColor() {
return color;
}
/**
* The color of text
*/
public void setColor(ColorStateList colorStateList) {
if (this.color == null || !this.color.equals(colorStateList)) {
this.color = colorStateList;
invalidateSelf();
}
}
/**
* The color of text
*/
public void setColor(int color) {
setColor(ColorStateList.valueOf(color));
}
/**
* Text size
*/
public void setTextSize(float size) {
if (this.textPaint.getTextSize() != size) {
this.textPaint.setTextSize(size);
measureSize();
invalidateSelf();
}
}
/**
* Text size
*/
public void setTextSize(int unit, float size, Context context) {
setTextSize(TypedValue.applyDimension(unit, size, context.getResources().getDisplayMetrics()));
}
/**
* This method is called by default when any property that may have some influence on the size
* of drawable This method should use measuredWidth and measuredHeight properties to store the
* measured walues By default the measuredWIdth and measuredHeight are set to iWidth and iHeight
* (size of text) by this method.
*/
protected void measureSize() {
ascent = -textPaint.ascent();
iWidth = (int) (0.5f + textPaint.measureText(text));
iHeight = (int) (0.5f + textPaint.descent() + ascent);
measuredWidth = iWidth;
measuredHeight = iHeight;
}
public float getTextSize() {
return textPaint.getTextSize();
}
#Override
protected boolean onStateChange(int[] state) {
int clr = color != null ? color.getColorForState(state, 0) : 0;
if (textPaint.getColor() != clr) {
textPaint.setColor(clr);
return true;
} else {
return false;
}
}
public Typeface getTypeface() {
return textPaint.getTypeface();
}
public void setTypeface(Typeface typeface) {
if (!textPaint.getTypeface().equals(typeface)) {
textPaint.setTypeface(typeface);
invalidateSelf();
}
}
/**
* The method is called before the text is drawn. This method can be overridden to draw some background (by default this method does nothing).
* #param canvas The canvas where to draw.
* #param bounds The bounds of the drawable.
*/
protected void drawBefore(Canvas canvas, Rect bounds) {
}
/**
* The method is called after the text is drawn. This method can be overriden to draw some more graphics over the text (by default this method does nothing).
* #param canvas The canvas where to draw.
* #param bounds The bound of the drawable.
*/
protected void drawAfter(Canvas canvas, Rect bounds) {
}
#Override
public void draw(Canvas canvas) {
if (text == null || text.isEmpty()) {
return;
}
final Rect bounds = getBounds();
int stack = canvas.save();
canvas.translate(bounds.left, bounds.top);
drawBefore(canvas, bounds);
if (text != null && !text.isEmpty()) {
final float x = bounds.width() >= iWidth ? bounds.centerX() : iWidth * 0.5f;
float y = 0;
switch (verticalAlignment) {
case BASELINE:
y = (bounds.height() - iHeight) * 0.5f + ascent;
break;
case TOP:
y = bounds.height();
break;
case BOTTOM:
y = bounds.height();
break;
}
canvas.drawText(text, x, y, textPaint);
}
drawAfter(canvas, bounds);
canvas.restoreToCount(stack);
}
#Override
public void setAlpha(int alpha) {
if (textPaint.getAlpha() != alpha) {
textPaint.setAlpha(alpha);
invalidateSelf();
}
}
#Override
public void setColorFilter(ColorFilter cf) {
if (textPaint.getColorFilter() == null || !textPaint.getColorFilter().equals(cf)) {
textPaint.setColorFilter(cf);
invalidateSelf();
}
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public enum VerticalAlignment {
TOP, BOTTOM, BASELINE
}
and how to use it:
fab.setImageDrawable(new TextDrawable(fab.getContext(), "FAB", ColorStateList.valueOf(Color.BLACK), 32.f, VerticalAlignment.BASELINE));
(fab is FloatingActionButton)

Error inflating class <unknown> - runtime exception

Does someone know what might be the source of an error which occurs randomly, without being able to identify a clear scenario?
java.lang.RuntimeException: Unable to start activity ComponentInfo {com.xxx/com.xxx.activities.SplashActivity}: android.view.InflateException: Binary XML file line #38: Error inflating class <unknown>
A fragment from the layout is posted below:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/dark_gray">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="#drawable/xl" />
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:gravity="center"
android:padding="10dp"
android:text="xxx"
android:textColor="#FFF"
android:textSize="24sp" />
</LinearLayout>
<RelativeLayout
android:id="#+id/welcomeContent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.xxx.utils.ParallaxViewPager // line 38
android:id="#+id/parallaxviewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg_parallax">
The class com.xxx.utils.ParallaxViewPager exists and I can catch the error very randomly.
public class ParallaxViewPager extends ViewPager {
public static final int FIT_WIDTH = 0;
public static final int FIT_HEIGHT = 1;
public static final float OVERLAP_FULL = 1f;
public static final float OVERLAP_HALF = 0.5f;
public static final float OVERLAP_QUARTER = 0.25f;
private static final float CORRECTION_PERCENTAGE = 0.01f;
public Bitmap bitmap;
private Rect source, destination;
private int scaleType;
private int chunkWidth;
private int projectedWidth;
private float overlap;
private OnPageChangeListener secondOnPageChangeListener;
private TextView[] dots;
private ScrollerCustomDuration mScroller = null;
float mStartDragX;
OnSwipeOutListener mListener;
public ParallaxViewPager(Context context) {
super(context);
init();
}
public ParallaxViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
postInitViewPager();
}
public void setScrollDurationFactor(double scrollFactor) {
mScroller.setScrollDurationFactor(scrollFactor);
}
public void setOnSwipeOutListener(OnSwipeOutListener listener) {
mListener = listener;
}
private void init() {
source = new Rect();
destination = new Rect();
scaleType = FIT_HEIGHT;
overlap = OVERLAP_HALF;
setOnPageChangeListener(new OnPageChangeListener() {
#Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (position < 3) {
if (bitmap != null) {
source.left = (int) Math.floor((position + positionOffset - CORRECTION_PERCENTAGE) * chunkWidth);
source.right = (int) Math.ceil((position + positionOffset + CORRECTION_PERCENTAGE) * chunkWidth + projectedWidth);
destination.left = (int) Math.floor((position + positionOffset - CORRECTION_PERCENTAGE) * getWidth());
destination.right = (int) Math.ceil((position + positionOffset + 1 + CORRECTION_PERCENTAGE) * getWidth());
invalidate();
}
if (secondOnPageChangeListener != null) {
secondOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
}
}
#Override public void onPageSelected(int position) {
if (secondOnPageChangeListener != null) {
secondOnPageChangeListener.onPageSelected(position);
}
for (int i = 0; i < 3; i++) {
dots[i].setTextColor(getResources().getColor(android.R.color.darker_gray));
}
if (position < 3) {
dots[position].setTextColor(getResources().getColor(R.color.tw__solid_white));
}
if (position == 3) {
mListener.onSwipeOutAtEnd();
}
}
#Override public void onPageScrollStateChanged(int state) {
if (secondOnPageChangeListener != null) {
secondOnPageChangeListener.onPageScrollStateChanged(state);
}
}
});
}
#Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
destination.top = 0;
destination.bottom = h;
if (getAdapter() != null && bitmap != null)
calculateParallaxParameters();
}
private void calculateParallaxParameters() {
if (bitmap.getWidth() < getWidth() && bitmap.getWidth() < bitmap.getHeight() && scaleType == FIT_HEIGHT) {
Log.w(ParallaxViewPager.class.getName(), "Invalid bitmap bounds for the current device, parallax effect will not work.");
}
final float ratio = (float) getHeight() / bitmap.getHeight();
if (ratio != 1) {
switch (scaleType) {
case FIT_WIDTH:
source.top = (int) ((bitmap.getHeight() - bitmap.getHeight() / ratio) / 2);
source.bottom = bitmap.getHeight() - source.top;
chunkWidth = (int) Math.ceil((float) bitmap.getWidth() / (float) getAdapter().getCount());
projectedWidth = chunkWidth;
break;
case FIT_HEIGHT:
default:
source.top = 0;
source.bottom = bitmap.getHeight();
projectedWidth = (int) Math.ceil(getWidth() / ratio);
chunkWidth = (int) Math.ceil((bitmap.getWidth() - projectedWidth) / (float) getAdapter().getCount() * overlap);
break;
}
}
}
/**
* Sets the background from a resource file.
*
* #param resid
*/
#Override public void setBackgroundResource(int resid) {
bitmap = BitmapFactory.decodeResource(getResources(), resid);
}
/**
* Sets the background from a Drawable.
*
* #param background
*/
#Override public void setBackground(Drawable background) {
bitmap = ((BitmapDrawable) background).getBitmap();
}
/**
* Deprecated.
* Sets the background from a Drawable.
*
* #param background
*/
#Override public void setBackgroundDrawable(Drawable background) {
bitmap = ((BitmapDrawable) background).getBitmap();
}
/**
* Sets the background from a bitmap.
*
* #param bitmap
* #return The ParallaxViewPager object itself.
*/
public ParallaxViewPager setBackground(Bitmap bitmap) {
this.bitmap = bitmap;
return this;
}
/**
* Sets how the view should scale the background. The available choices are:
* <ul>
* <li>FIT_HEIGHT - the height of the image is resized to matched the height of the View, also stretching the width to keep the aspect ratio. The non-visible part of the bitmap is divided into equal parts, each of them sliding in at the proper position.</li>
* <li>FIT_WIDTH - the width of the background image is divided into equal chunks, each taking up the whole width of the screen.</li>
* </ul>
*
* #param scaleType
* #return
*/
public ParallaxViewPager setScaleType(final int scaleType) {
if (scaleType != FIT_WIDTH && scaleType != FIT_HEIGHT)
throw new IllegalArgumentException("Illegal argument: scaleType must be FIT_WIDTH or FIT_HEIGHT");
this.scaleType = scaleType;
return this;
}
/**
* Sets the amount of overlapping with the setOverlapPercentage(final float percentage) method. This is a number between 0 and 1, the smaller it is, the slower is the background scrolling.
*
* #param percentage
* #return The ParallaxViewPager object itself.
*/
public ParallaxViewPager setOverlapPercentage(final float percentage) {
if (percentage <= 0 || percentage >= 1)
throw new IllegalArgumentException("Illegal argument: percentage must be between 0 and 1");
overlap = percentage;
return this;
}
/**
* Recalculates the parameters of the parallax effect, useful after changes in runtime.
*
* #return The ParallaxViewPager object itself.
*/
public ParallaxViewPager invalidateParallaxParameters() {
calculateParallaxParameters();
return this;
}
#Override protected void onDraw(Canvas canvas) {
if (bitmap != null)
canvas.drawBitmap(bitmap, source, destination, null);
}
public void addOnPageChangeListener(OnPageChangeListener listener) {
secondOnPageChangeListener = listener;
}
public TextView[] getDots() {
return dots;
}
public void setDots(TextView[] dots) {
this.dots = dots;
}
private void postInitViewPager() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = viewpager.getDeclaredField("sInterpolator");
interpolator.setAccessible(true);
mScroller = new ScrollerCustomDuration(getContext(),
(Interpolator) interpolator.get(null));
scroller.set(this, mScroller);
} catch (Exception e) {
Log.e("MyPager", e.getMessage());
}
}
class ScrollerCustomDuration extends Scroller {
private double mScrollFactor = 2;
public ScrollerCustomDuration(Context context) {
super(context);
}
public ScrollerCustomDuration(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public ScrollerCustomDuration(Context context,
Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScrollFactor = scrollFactor;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy,
int duration) {
super.startScroll(startX, startY, dx, dy,
(int) (duration * mScrollFactor));
}
}
public interface OnSwipeOutListener {
public void onSwipeOutAtEnd();
}}
Thanks for your input.

Make Coverflow appear like a Horizontal Gallery Android

I am trying to make the CoverFlow below appear like a Horizontal Gallery. The original file looks like this
There are three classes
the CoverFlow.class which extends a gallery
public class CoverFlow extends Gallery {
private Camera mCamera = new Camera();
private int mMaxRotationAngle = 50;
private int mMaxZoom = -380;
private int mCoveflowCenter;
private boolean mAlphaMode = true;
private boolean mCircleMode = false;
public CoverFlow(Context context) {
super(context);
this.setStaticTransformationsEnabled(true);
}
public CoverFlow(Context context, AttributeSet attrs) {
super(context, attrs);
this.setStaticTransformationsEnabled(true);
}
public CoverFlow(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setStaticTransformationsEnabled(true);
}
public int getMaxRotationAngle() {
return mMaxRotationAngle;
}
public void setMaxRotationAngle(int maxRotationAngle) {
mMaxRotationAngle = maxRotationAngle;
}
public boolean getCircleMode() {
return mCircleMode;
}
public void setCircleMode(boolean isCircle) {
mCircleMode = isCircle;
}
public boolean getAlphaMode() {
return mAlphaMode;
}
public void setAlphaMode(boolean isAlpha) {
mAlphaMode = isAlpha;
}
public int getMaxZoom() {
return mMaxZoom;
}
public void setMaxZoom(int maxZoom) {
mMaxZoom = maxZoom;
}
private int getCenterOfCoverflow() {
return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2
+ getPaddingLeft();
}
private static int getCenterOfView(View view) {
return view.getLeft() + view.getWidth() / 2;
}
protected boolean getChildStaticTransformation(View child, Transformation t) {
final int childCenter = getCenterOfView(child);
final int childWidth = child.getWidth();
int rotationAngle = 0;
t.clear();
t.setTransformationType(Transformation.TYPE_MATRIX);
if (childCenter == mCoveflowCenter) {
transformImageBitmap((ImageView) child, t, 0);
} else {
rotationAngle = (int) (((float) (mCoveflowCenter - childCenter) / childWidth) * mMaxRotationAngle);
if (Math.abs(rotationAngle) > mMaxRotationAngle) {
rotationAngle = (rotationAngle < 0) ? -mMaxRotationAngle
: mMaxRotationAngle;
}
transformImageBitmap((ImageView) child, t, rotationAngle);
}
return true;
}
/**
* This is called during layout when the size of this view has changed. If
* you were just added to the view hierarchy, you're called with the old
* values of 0.
*
* #param w
* Current width of this view.
* #param h
* Current height of this view.
* #param oldw
* Old width of this view.
* #param oldh
* Old height of this view.
*/
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCoveflowCenter = getCenterOfCoverflow();
super.onSizeChanged(w, h, oldw, oldh);
}
/**
* Transform the Image Bitmap by the Angle passed
*
* #param imageView
* ImageView the ImageView whose bitmap we want to rotate
* #param t
* transformation
* #param rotationAngle
* the Angle by which to rotate the Bitmap
*/
private void transformImageBitmap(ImageView child, Transformation t,
int rotationAngle) {
mCamera.save();
final Matrix imageMatrix = t.getMatrix();
final int imageHeight = child.getLayoutParams().height;
final int imageWidth = child.getLayoutParams().width;
final int rotation = Math.abs(rotationAngle);
mCamera.translate(0.0f, 0.0f, 100.0f);
// As the angle of the view gets less, zoom in
if (rotation <= mMaxRotationAngle) {
float zoomAmount = (float) (mMaxZoom + (rotation * 1.5));
mCamera.translate(0.0f, 0.0f, zoomAmount);
if (mCircleMode) {
if (rotation < 40)
mCamera.translate(0.0f, 155, 0.0f);
else
mCamera.translate(0.0f, (255 - rotation * 2.5f), 0.0f);
}
if (mAlphaMode) {
((ImageView) (child)).setAlpha((int) (255 - rotation * 2.5));
}
}
mCamera.rotateY(rotationAngle);
mCamera.getMatrix(imageMatrix);
imageMatrix.preTranslate(-(imageWidth ), -(imageHeight / 2));
imageMatrix.postTranslate((imageWidth ), (imageHeight / 2));
mCamera.restore();
}
}
The second class is my coverFlowActivity which is my class class
public class CoverFlowActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.main);
CoverFlow cf = new CoverFlow(this);
ImageAdapter ia = new ImageAdapter(this);
cf.setAdapter(ia);
cf.setAnimationDuration(1000);
setContentView(cf);
}
public class ImageAdapter extends BaseAdapter {
private int[] mImgs = {
R.drawable.img1,
R.drawable.img2,
R.drawable.img3,
R.drawable.img4,
R.drawable.img5,
R.drawable.img6,
R.drawable.img7,
R.drawable.img8
};
Context mContext;
public ImageAdapter(Context context) {
this.mContext = context;
}
#Override
public int getCount() {
return mImgs.length;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return mImgs[position];
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ReflectionImage i = new ReflectionImage(mContext);
i.setImageResource(mImgs[position]);
i.setLayoutParams(new CoverFlow.LayoutParams(100, 100));
i.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
BitmapDrawable drawable = (BitmapDrawable) i.getDrawable();
drawable.setAntiAlias(true);
return i;
}
public float getScale(boolean focused, int offset) {
return Math.max(0, 1f/(float)Math.pow(2, Math.abs(offset)));
}
}
}
and the third activity is the reflected image below each image.
public class ReflectionImage extends ImageView {
//是否为Reflection模式
private boolean mReflectionMode = true;
public ReflectionImage(Context context) {
super(context);
}
public ReflectionImage(Context context, AttributeSet attrs) {
super(context, attrs);
//取得原始图片的bitmap并重画
Bitmap originalImage = ((BitmapDrawable)this.getDrawable()).getBitmap();
DoReflection(originalImage);
}
public ReflectionImage(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
Bitmap originalImage = ((BitmapDrawable)this.getDrawable()).getBitmap();
DoReflection(originalImage);
}
public void setReflectionMode(boolean isRef) {
mReflectionMode = isRef;
}
public boolean getReflectionMode() {
return mReflectionMode;
}
//偷懒了,只重写了setImageResource,和构造函数里面干了同样的事情
#Override
public void setImageResource(int resId) {
Bitmap originalImage = BitmapFactory.decodeResource(
getResources(), resId);
DoReflection(originalImage);
//super.setImageResource(resId);
}
private void DoReflection(Bitmap originalImage) {
final int reflectionGap = 4; //原始图片和反射图片中间的间距
int width = originalImage.getWidth();
int height = originalImage.getHeight();
//反转
Matrix matrix = new Matrix();
matrix.preScale(1, -1);
//reflectionImage就是下面透明的那部分,可以设置它的高度为原始的3/4,这样效果会更好些
Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0,
0, width, height, matrix, false);
//创建一个新的bitmap,高度为原来的两倍
Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + height), Config.ARGB_8888);
Canvas canvasRef = new Canvas(bitmapWithReflection);
//先画原始的图片
canvasRef.drawBitmap(originalImage, 0, 0, null);
//画间距
Paint deafaultPaint = new Paint();
canvasRef.drawRect(0, height, width, height + reflectionGap, deafaultPaint);
//画被反转以后的图片
canvasRef.drawBitmap(reflectionImage, 0, height + reflectionGap, null);
// 创建一个渐变的蒙版放在下面被反转的图片上面
Paint paint = new Paint();
LinearGradient shader = new LinearGradient(0,
originalImage.getHeight(), 0, bitmapWithReflection.getHeight()
+ reflectionGap, 0x80ffffff, 0x00ffffff, TileMode.CLAMP);
// Set the paint to use this shader (linear gradient)
paint.setShader(shader);
// Set the Transfer mode to be porter duff and destination in
paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
// Draw a rectangle using the paint with our linear gradient
canvasRef.drawRect(0, height, width, bitmapWithReflection.getHeight()
+ reflectionGap, paint);
//调用ImageView中的setImageBitmap
this.setImageBitmap(bitmapWithReflection);
}
}
I want to make it appears like a gallery that is all images must have four dp margin and appear like the center image without any effects. Can someone please help me to achieve this.
Please try this:
private int mMaxRotationAngle = 0;
and
CoverFlow cf = new CoverFlow(this);
cf.setSpacing(-30);
in the CoverFlowActivity
From what I understood, you need a ViewPager:
http://developer.android.com/reference/android/support/v4/view/ViewPager.html
NOTE: add the support library to support API till level 4 ;-)
Some usefull code: http://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html
setContentView(R.layout.main);
mContainer = (PagerContainer) findViewById(R.id.pager_container);
ViewPager pager = mContainer.getViewPager();
PagerAdapter adapter = new MyPagerAdapter();
pager.setAdapter(adapter);
//Necessary or the pager will only have one extra page to show
// make this at least however many pages you can see
pager.setOffscreenPageLimit(adapter.getCount());
//A little space between pages
pager.setPageMargin(15);
//If hardware acceleration is enabled, you should also remove
// clipping on the pager for its children.
pager.setClipChildren(false);
Adding padding and margin should be trivial ;-)

Categories

Resources