I have a custom view named as HorizontalScaleView
public class HorizontalScaleView extends View {
private static float THICK_STROKE_WIDTH = 16.0f;
private static float THIN_STROKE_WIDTH = 8.0f;
private static float THINNER_STROKE_WIDTH = 4.0f;
private int mColorStartGradient;
private int mColorEndGradient;
private int mColorOptimum;
private int mColorLineGradientStart;
private int mColorLineGradientEnd;
private int mColorDarkerGrey;
private int mMaxOptimumValue = 1000;
private Paint mLinePaint;
private Paint mTargetPaint;
private int mWidth;
private int mHeight;
private int markerValue = 0;
private int targetValue = 700;
private int averageValue = -1;
private Drawable mMarkerDrawable;
private Drawable mTargetDrawable;
private Rect markerRect = new Rect();
private Rect targetRect = new Rect();
public HorizontalScaleView(Context context) {
super(context);
mColorStartGradient = ContextCompat.getColor(context, R.color.colorBackGradientStart);
mColorEndGradient = ContextCompat.getColor(context, R.color.score_maintain);
mColorOptimum = ContextCompat.getColor(context, R.color.score_maintain);
mColorDarkerGrey = ContextCompat.getColor(context, R.color.textColorDarkGrey);
init(context);
}
public HorizontalScaleView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.HorizontalScaleView,
0, 0);
try {
mColorStartGradient = attributes.getColor(R.styleable.HorizontalScaleView_color_back_gradient_start,
ContextCompat.getColor(context, R.color.colorBackGradientStart));
mColorEndGradient = attributes.getColor(R.styleable.HorizontalScaleView_color_back_gradient_end,
ContextCompat.getColor(context, R.color.score_maintain));
mColorOptimum = attributes.getColor(R.styleable.HorizontalScaleView_color_optimum,
ContextCompat.getColor(context, R.color.score_maintain));
mColorLineGradientStart = attributes.getColor(R.styleable.HorizontalScaleView_color_gradient_start,
ContextCompat.getColor(context, R.color.line_gradient_start));
mColorLineGradientEnd = attributes.getColor(R.styleable.HorizontalScaleView_color_gradient_end,
ContextCompat.getColor(context, R.color.line_gradient_end));
mColorDarkerGrey = attributes.getColor(R.styleable.HorizontalScaleView_color_marker,
ContextCompat.getColor(context, R.color.textColorDarkGrey));
mMaxOptimumValue = attributes.getInteger(R.styleable.HorizontalScaleView_max_optimum_value,
1000);
targetValue = attributes.getInteger(R.styleable.HorizontalScaleView_target_value,
0);
averageValue = attributes.getInteger(R.styleable.HorizontalScaleView_average_value,
-1);
} finally {
attributes.recycle();
}
init(context);
}
private void init(Context context) {
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setStrokeWidth(THINNER_STROKE_WIDTH);
mTargetPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTargetPaint.setStrokeWidth(THINNER_STROKE_WIDTH);
mTargetPaint.setColor(ContextCompat.getColor(context, android.R.color.black));
mMarkerDrawable = context.getDrawable(R.drawable.ic_marker);
mTargetDrawable = context.getDrawable(R.drawable.ic_arrow_drop_up_black);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mWidth, mHeight);
}
#SuppressLint("DrawAllocation")
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
mWidth = getWidth();
int gap = (mHeight * 10) / 100;
int sidePadding = 2 * gap;
mWidth -= 2 * sidePadding;
mLinePaint.setTextSize((mHeight * 15) / 100);
mTargetPaint.setTextSize((mHeight * 15) / 100);
float optimumStopX = mWidth + sidePadding;
THIN_STROKE_WIDTH = (mHeight * 25) / 1000;
THINNER_STROKE_WIDTH = (mHeight * 15) / 1000;
Shader shader = new LinearGradient(sidePadding, mHeight / 2, optimumStopX, mHeight / 2,
mColorLineGradientStart, mColorLineGradientEnd, Shader.TileMode.CLAMP);
mLinePaint.setShader(shader);
mLinePaint.setStrokeWidth(mHeight * 4 / 100);
canvas.drawLine(sidePadding, mHeight * 32 / 100, optimumStopX, mHeight * 32 / 100, mLinePaint);
mLinePaint.setShader(null);
//Drawing background Gradient
int gradientHeight = (mHeight * 16) / 100;
shader = new LinearGradient(0, gradientHeight, mWidth + sidePadding + THIN_STROKE_WIDTH, gradientHeight,
mColorStartGradient, mColorEndGradient, Shader.TileMode.CLAMP);
mLinePaint.setShader(shader);
mLinePaint.setStrokeWidth(mHeight * 32 / 100);
mLinePaint.setAlpha(30);
canvas.drawLine(0, gradientHeight, mWidth + sidePadding + THIN_STROKE_WIDTH, gradientHeight, mLinePaint);
mLinePaint.setShader(null);
int borderLineHeight = (mHeight * 5) / 100;
mLinePaint.setAlpha(0);
mLinePaint.setStrokeWidth(THIN_STROKE_WIDTH);
mLinePaint.setColor(mColorLineGradientEnd);
canvas.drawLine(optimumStopX, (mHeight * 32 / 100) - borderLineHeight, optimumStopX, (mHeight * 32 / 100) + borderLineHeight, mLinePaint);
mLinePaint.setColor(mColorLineGradientStart);
canvas.drawLine(sidePadding, (mHeight * 32 / 100) - borderLineHeight, sidePadding, (mHeight * 32 / 100) + borderLineHeight, mLinePaint);
// Drawing Grade i.e 0 ' 100 ' 200 ' 300 ....... 1000
int gradeWidth = (int) ((optimumStopX - sidePadding) / 10);
int gradePositionX = sidePadding;
int gradePositionY = (mHeight * 44) / 100;
int gradeHeight = (mHeight * 3) / 100;
int gradeTextHeight = (mHeight * 58) / 100;
mLinePaint.setStrokeWidth(THIN_STROKE_WIDTH);
mLinePaint.setTextSize((mHeight * 10) / 100);
canvas.drawText("0", gradePositionX, gradeTextHeight, mLinePaint);
for (int index = 1; index < 10; index++) {
gradePositionX += gradeWidth;
if (index % 2 == 0) {
mLinePaint.setStrokeWidth(THIN_STROKE_WIDTH);
canvas.drawLine(gradePositionX, gradePositionY - gradeHeight, gradePositionX,
gradePositionY + gradeHeight, mLinePaint);
if ((index * 100) != targetValue) {
mLinePaint.setStrokeWidth(THIN_STROKE_WIDTH);
canvas.drawText(String.valueOf(index * 100), gradePositionX - ((mHeight * 8) / 100), gradeTextHeight, mLinePaint);
}
} else {
mLinePaint.setStrokeWidth(THINNER_STROKE_WIDTH);
canvas.drawLine(gradePositionX, gradePositionY - gradeHeight, gradePositionX,
gradePositionY + gradeHeight, mLinePaint);
}
}
canvas.drawText("1000", optimumStopX - ((mHeight * 22) / 100), gradeTextHeight, mLinePaint);
//Drawing Current Position
int markerPositionX = (int) (((optimumStopX - sidePadding) / mMaxOptimumValue) * markerValue);
markerPositionX += sidePadding;
int markerPositionY = mHeight * 34 / 100;
int markerWidth = (mHeight * 5 / 100);
int markerHeight = (mHeight * 65 / 1000);
mMarkerDrawable.setTint(mColorDarkerGrey);
markerRect.set(markerPositionX - 2 * markerWidth, markerPositionY - (5 * markerHeight),
markerPositionX + 2 * markerWidth, markerPositionY - markerHeight);
mMarkerDrawable.setBounds(markerRect);
mMarkerDrawable.draw(canvas);
canvas.restore();
//Drawing Target Position
if (targetValue <= 0) {
return;
}
mTargetPaint.setColor(mColorOptimum);
mTargetPaint.setStrokeWidth(THIN_STROKE_WIDTH);
mTargetPaint.setTextSize(mHeight * 18 / 100);
int targetPositionX = (int) (((optimumStopX - sidePadding) / mMaxOptimumValue) * targetValue);
int targetPositionY = mHeight * 58 / 100;
targetPositionX += sidePadding;
int targetWidth = (mHeight * 5 / 100);
int targetHeight = (mHeight * 4 / 100);
targetRect.set(targetPositionX - 2 * targetWidth, targetPositionY - targetHeight,
targetPositionX + 2 * targetWidth, targetPositionY + (4 * targetHeight));
mTargetDrawable.setTint(mColorOptimum);
mTargetDrawable.setBounds(targetRect);
mTargetDrawable.draw(canvas);
canvas.drawText(String.valueOf(targetValue), targetPositionX - (mHeight * 15 / 100), (mHeight * 82) / 100, mTargetPaint);
mTargetPaint.setTextSize(mHeight * 10 / 100);
canvas.drawText("Target", targetPositionX - (mHeight * 14 / 100), (mHeight * 95) / 100, mTargetPaint);
//Drawing Average Value
if (averageValue <= 0) {
return;
}
mTargetPaint.setColor(mColorDarkerGrey);
mTargetPaint.setStrokeWidth(THIN_STROKE_WIDTH);
mTargetPaint.setTextSize(mHeight * 18 / 100);
int averageValuePosition = (int) (((optimumStopX - sidePadding) / mMaxOptimumValue) * averageValue);
averageValuePosition += sidePadding;
targetRect.set(averageValuePosition - 2 * targetWidth, targetPositionY - targetHeight,
averageValuePosition + 2 * targetWidth, targetPositionY + (4 * targetHeight));
mTargetDrawable.setBounds(targetRect);
mTargetDrawable.draw(canvas);
canvas.drawText(String.valueOf(averageValue), averageValuePosition - 65, (mHeight * 82) / 100, mTargetPaint);
mTargetPaint.setTextSize(mHeight * 10 / 100);
canvas.drawText("Average", averageValuePosition - 70, (mHeight * 95) / 100, mTargetPaint);
mTargetPaint.setColorFilter(null);
}
public void setMarkerValue(int markerValue) {
this.markerValue = markerValue;
invalidate();
requestLayout();
}
public void setTargetValue(int targetValue) {
this.targetValue = targetValue;
invalidate();
requestLayout();
}
}
I used it in one of my layout files:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white"
android:orientation="vertical">
<TextView
android:id="#+id/score_card_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="12dp"
android:layout_marginRight="16dp"
android:text="#string/dashboard_household_score_card_desc"
android:textColor="#color/textColorDarkGrey"
android:textSize="14sp" />
<TextView
android:id="#+id/score_card_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/score_card_desc"
android:layout_marginLeft="16dp"
android:layout_marginTop="-8dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="-8dp"
android:fontFamily="sans-serif-light"
android:textColor="#color/textColorDarkGrey"
android:textSize="100sp"
tools:text="655" />
<com.kroger.mobile.components.HorizontalScaleView
android:id="#+id/score_card_graph"
style="#style/HorizontalScaleView"
android:layout_width="400dp"
android:layout_height="120dp"
android:layout_below="#+id/score_card_score"
android:layout_centerHorizontal="true"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
app:max_optimum_value="1000"
app:target_value="#integer/dashboard_target" />
</RelativeLayout>
</layout>
Now I want to take screenshot of this layout programmatically without displaying it on the screen and then and share it
I use these functions to generate, store and share screenshot respectively
private Bitmap generateScreenShot(Household household) {
LayoutDashboardScreenShotBinding shareBinding =
DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.layout_dashboard_screen_shot, null, false);
shareBinding.scoreCardScore.setText(String.valueOf(household.overallScore));
shareBinding.scoreCardGraph.setMarkerValue(household.overallScore);
shareBinding.scoreCardGraph.setTargetValue(0);
View shareView = shareBinding.getRoot();
shareView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
shareView.layout(0, 0, shareView.getMeasuredWidth(), shareView.getMeasuredHeight());
Bitmap bitmap = Bitmap.createBitmap(shareView.getWidth(), shareView.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
shareView.draw(canvas);
return bitmap;
}
public File storeScreenShot(Bitmap bm, String fileName){
String dirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + DIRECTORY_PATH;
File dir = new File(dirPath);
if(dir.exists() || dir.mkdirs()) {
File file = new File(dirPath, fileName);
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
return file;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
private void shareImage(File file){
Context context = getContext();
if(context == null){
return;
}
Uri uri = FileProvider.getUriForFile(context,
context.getApplicationContext().getPackageName() + ".provider", file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("image/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "");
intent.putExtra(android.content.Intent.EXTRA_TEXT, "");
intent.putExtra(Intent.EXTRA_STREAM, uri);
try {
startActivity(Intent.createChooser(intent, "Share Screenshot"));
} catch (ActivityNotFoundException e) {
Toast.makeText(context, "No App Available", Toast.LENGTH_SHORT).show();
}
}
What I expect to capture in that screenshot is:
But what I am getting is:
The custom view that I have in this layout is not showing up on the screenshot.
Maybe HorizontalScaleView.setMeasuredDimensions() is getting called with 0,0 because you did not handle the case of MeasureSpecs like UNSPECIFIED, which would have a getSize() of 0 :)
Related
In android I have create custom view.I have first drawn circle and now I want to draw a line angle wise within a circle. I want like this with animation.
https://drive.google.com/file/d/1Qx0MBu-77JIlQTByqGTyD-KtGKOB8naG/view?usp=sharing
I have used canvas to draw circle and lines.I have taken viewpager.If I swipe viewpager then pie graphics will rotate.
What I have done uptill now. When animating it's always start from zero:
https://drive.google.com/file/d/12mmAUOeY77jAlj_GmM3Ymcx5m34vli3X/view?usp=sharing
I have done below code:
public class PieView : View
{
int w, h, pl, pr, pt, pb, usableWidth, usableHeight, radius, cx, cy, lineLenght;
Paint paint;
public Canvas canvas;
public float firstLineangle = 0;
public float secondLineangle = 40;
public float thirdLineangle = 120;
float currentAngle,maxAngle;
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
w = Width;
h = Height;
pl = PaddingLeft;
pr = PaddingRight;
pt = PaddingTop;
pb = PaddingBottom;
this.canvas = canvas;
usableWidth = w - (pl + pr);
usableHeight = h - (pt + pb);
radius = Math.Min(usableWidth, usableHeight) / 2;
cx = pl + (usableWidth / 2);
cy = pt + (usableHeight / 2);
lineLenght = radius - (pl * 2) - (pr * 2);
paint = new Paint();
paint.Color = Android.Graphics.Color.White;
paint.SetStyle(Paint.Style.Stroke);
paint.StrokeWidth = 5;
canvas.DrawCircle(cx, cy, radius - 5, paint);
Drawline(canvas, firstLineangle);
Drawline(canvas, secondLineangle);
Drawline(canvas, thirdLineangle);
PostInvalidateDelayed(500);
Invalidate();
}
public void Drawline(Canvas canvas, float angle)
{
float displacedAngle = angle - 90;
float x = cx + ((float)Math.Cos(degreesToRadians(displacedAngle)) * (radius - 5)); //convert angle to radians for x and y coordinates
float y = cy + ((float)Math.Sin(degreesToRadians(displacedAngle)) * (radius - 5));
canvas.DrawLine(cx, cy, x, y, paint); //draw a line from center point back to the point
}
public double degreesToRadians(double degrees)
{
return (degrees * Math.PI) / 180;
}
}
public class PieAnimation : Android.Views.Animations.Animation
{
private PieView pieView;
private float firstLineangle;
private float secondLineangle;
private float thirdLineangle;
public PieAnimation(PieView pieView, float firstLineangle,float secondLineangle,float thirdLineangle)
{
this.pieView = pieView;
this.firstLineangle = firstLineangle;
this.secondLineangle = secondLineangle;
this.thirdLineangle = thirdLineangle;
}
protected override void ApplyTransformation(float interpolatedTime, Transformation t)
{
pieView.firstLineangle = 0 + ((firstLineangle) * interpolatedTime);
pieView.secondLineangle = 0 + ((secondLineangle) * interpolatedTime);
pieView.thirdLineangle = 0 + ((thirdLineangle) * interpolatedTime);
pieView.RequestLayout();
}
}
public class TourPager : Java.Lang.Object, ViewPager.IOnPageChangeListener, ViewPager.IPageTransformer
{
private ViewPager mViewPager;
private float mLastOffset;
public TourView _context;
public TourPager(ViewPager viewpager, TourView context)
{
mViewPager = viewpager;
viewpager.AddOnPageChangeListener(this);
_context = context;
}
public void OnPageSelected(int position)
{
if (position == 0)
{
PieAnimation animation = new PieAnimation(_context._pieView, 0, 40, 120);
animation.Duration = (1000);
_context._pieView.StartAnimation(animation);
}
if (position==1)
{
PieAnimation animation = new PieAnimation(_context._pieView, 100, 140, 200);
animation.Duration=(1000);
_context._pieView.StartAnimation(animation);
}
if(position==2)
{
PieAnimation animation = new PieAnimation(_context._pieView, 180, 270, 10);
animation.Duration = (1000);
_context._pieView.StartAnimation(animation);
}
}
There is running GIF.
There is PieView.cs.
public class PieView:View
{
int w, h, pl, pr, pt, pb, usableWidth, usableHeight, radius, cx, cy, lineLenght;
int handTruncation, hourHandTruncation = 0;
Paint paint;
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
Rect rect = new Rect();
public PieView(Context context) : base(context)
{
}
public PieView(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public PieView(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
{
}
public PieView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
{
}
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
w = Width;
h = Height;
pl = PaddingLeft+10;
pr = PaddingRight+10;
pt = PaddingTop+10;
pb = PaddingBottom+10;
usableWidth = w - (pl + pr);
usableHeight = h - (pt + pb);
radius = Math.Min(usableWidth, usableHeight) / 2;
cx = pl + (usableWidth / 2);
cy = pt + (usableHeight / 2);
int min = Math.Min(usableWidth, usableHeight);
handTruncation = min / 20;
hourHandTruncation = min / 7;
lineLenght = radius - (pl * 2) - (pr * 2);
paint = new Paint();
paint.Color = Android.Graphics.Color.White;
paint.SetStyle(Paint.Style.Stroke);
paint.StrokeWidth = 5;
canvas.DrawCircle(cx, cy, radius , paint);
drawNumeral(canvas);
drawHands(canvas);
PostInvalidateDelayed(200);
Invalidate();
}
private void drawHands(Canvas canvas)
{
Calendar c = Calendar.Instance;
float hour = c.Get(CalendarField.HourOfDay);
hour = hour > 12 ? hour - 12 : hour;
drawHand1(canvas, (hour + c.Get(CalendarField.Minute) / 60) * 5f,true);
drawHand1(canvas, c.Get(CalendarField.Minute),false);
drawHand1(canvas, c.Get(CalendarField.Second),false);
}
private void drawNumeral(Canvas canvas)
{
paint.TextSize=50;
foreach (var number in numbers)
{
string tmp = number.ToString();
paint.GetTextBounds( tmp, 0, tmp.Length, rect); //getTextBounds(tmp, 0, tmp.length(), rect);
double angle = Math.PI / 6 * (number - 3);
int x = (int)(w / 2 + Math.Cos(angle) * radius - rect.Width() / 2);
int y = (int)(h / 2 + Math.Sin(angle) * radius + rect.Height() / 2);
canvas.DrawText(tmp, x, y, paint);
}
}
private void drawHand1(Canvas canvas, double loc, bool isHour)
{
double angle = Math.PI * loc / 30 - Math.PI / 2;
int handRadius = isHour ? radius - handTruncation - hourHandTruncation : radius - handTruncation;
canvas.DrawLine(Width / 2, Height / 2,
(float)(Width / 2 + Math.Cos(angle) * handRadius),
(float)(Height / 2 + Math.Sin(angle) * handRadius),
paint);
}
}
You can use it in MainActivity.cs
RelativeLayout relativeLayout1 = FindViewById<RelativeLayout>
(Resource.Id.relativeLayout1);
relativeLayout1.SetBackgroundColor(Color.Black);
AddContentView(new PieView(this),new ViewGroup.LayoutParams(-1,-1));
I've created a custom view that draws a dial gauge. If I set a static value for the angle of the needle, the gauge draws as expected (see first image below). If I attempt to set the angle of the needle through runOnUiThread, then I have weird needle artifacts (see second image).
I am calling canvas.drawColor(color.BLACK) each time onDraw() is called, so I am not sure how the artifacts are being carried over. My best guess is that I am not handling threading correctly in the method that calls runOnUiThread().
DialGaugeView class:
package net.dynu.kubie.redneksldhlr;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
public class DialGaugeView extends View {
private int height, width, min = 0;
private int numberFontSize, titleFontSize = 0;
private float dialEdgeStroke, dialEdgeRadius = 0;
private float dialFaceRadius = 0;
private float tickMajorStroke, tickSweep, tickAngleStart, tickAngleEnd, tickEdgeRadius, tickInnerMajorRadius, tickInnerMinorRadius = 0;
private int tickMajorCount = 0;
private float tickMinorStroke = 0;
private int tickMinorCount = 0;
private int tickTotalCount = 0;
private int numberMin, numberMax = 0;
private float numberRadius = 0;
private float arrowTipRadius, arrowRearRadius = 0;
private float arrowCenterRadius, arrowPinRadius = 0;
private float sensorInput = 0;
private float temp = 0;
private Paint paint;
private boolean isInit;
private float titleRadius = 0;
private Rect rect = new Rect();
private Path path = new Path();
private String titleStr = "";
//Variables common to drawing functions
private float center_x, center_y = 0;
private float x1, x2, x3, y1, y2, y3 = 0;
private float angle = 0;
private int number = 0;
private String str = "";
public DialGaugeView(Context context) {
super(context);
}
public DialGaugeView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.DialGaugeView,
0, 0);
try {
tickMajorCount = a.getInteger(R.styleable.DialGaugeView_tickCountMajor, 5);
tickMinorCount = a.getInteger(R.styleable.DialGaugeView_tickCountMinor, 0);
tickSweep = a.getFloat(R.styleable.DialGaugeView_tickSweep, 270) / 2;
numberMin = a.getInteger(R.styleable.DialGaugeView_displayMin, 0);
numberMax = a.getInteger(R.styleable.DialGaugeView_displayMax, 1);
titleStr = a.getString(R.styleable.DialGaugeView_displayTitle);
} finally {
a.recycle();
}
}
public DialGaugeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public int getTickMajorCount() {
return tickMajorCount;
}
public void setTickMajorCount(int i) {
tickMajorCount = i;
isInit = false;
invalidate();
requestLayout();
}
public int getTickMinorCount() {
return tickMinorCount;
}
public void setTickMinorCount(int i) {
tickMinorCount = i;
isInit = false;
invalidate();
requestLayout();
}
public float getTickSweep() {
return tickSweep;
}
public void setTickSweep(float i) {
tickSweep = i / 2;
isInit = false;
invalidate();
requestLayout();
}
public int getDisplayMin() {
return numberMin;
}
public void setDisplayMin(int i) {
numberMin = i;
isInit = false;
invalidate();
requestLayout();
}
public int getDisplayMax() {
return numberMax;
}
public void setDisplayMax(int i) {
numberMax = i;
isInit = false;
invalidate();
requestLayout();
}
public float getSensorInput() {
return sensorInput;
}
public void setSensorInput(float i) {
sensorInput = i;
isInit = false;
invalidate();
requestLayout();
}
public String getTitle() {
return titleStr;
}
public void setTitle(String i) {
titleStr = i;
invalidate();
requestLayout();
}
private void initGauge() {
//Common variables
height = getHeight();
width = getWidth();
min = Math.min(height, width);
center_x = width / 2;
center_y = height / 2;
paint = new Paint();
isInit = true;
//Dial face variables
dialEdgeStroke = min / 20;
dialEdgeRadius = min / 2 - dialEdgeStroke / 2;
dialFaceRadius = dialEdgeRadius - dialEdgeStroke / 2;
//Tick variables
//tickMajorCount = 7; //TODO - Class input needed
//tickMinorCount = 1; //TODO - Class input needed
//tickSweep = 270 / 2; //TODO - Class input needed //Degrees +/- from 90 degrees
tickEdgeRadius = dialFaceRadius - dialEdgeStroke / 2;
tickInnerMajorRadius = (float) (tickEdgeRadius - dialEdgeStroke * 1.5);
tickInnerMinorRadius = ((tickEdgeRadius + tickInnerMajorRadius) / 2);
tickMajorStroke = min / 75;
tickAngleStart = 90 + tickSweep;
tickAngleEnd = 90 - tickSweep;
tickMinorStroke = tickMajorStroke / 2;
tickTotalCount = tickMajorCount + (tickMajorCount - 1) * tickMinorCount;
//Numeral variables
//numberMin = 0; //TODO - Class input needed
//numberMax = 120; //TODO - Class input needed
numberRadius = (tickInnerMajorRadius); // - (tickEdgeRadius - tickInnerMajorRadius) * 1.25);
numberFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, min / 40,
getResources().getDisplayMetrics());
//Title variables
titleRadius = tickInnerMajorRadius;
titleFontSize = numberFontSize;
//Arrow variables
//sensorInput = (float) (numberMax * .4965); //TODO - Class input needed
arrowTipRadius = tickEdgeRadius;
arrowRearRadius = arrowTipRadius / 2;
arrowCenterRadius = (arrowTipRadius * 1 / 8);
arrowPinRadius = (arrowTipRadius * 1 / 24);
}
#Override
protected void onDraw(Canvas canvas) {
if (!isInit) {
initGauge();
}
canvas.drawColor(Color.BLACK);
drawDialFace(canvas);
drawTicks(canvas);
drawNumeral(canvas);
drawTitle(canvas);
drawArrow(canvas);
postInvalidateDelayed(500);
invalidate();
requestLayout();
}
private void drawDialFace(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.black));
paint.setStrokeWidth(dialEdgeStroke);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
canvas.drawCircle(center_x, center_y, dialEdgeRadius, paint);
paint.reset();
paint.setColor(getResources().getColor(android.R.color.white));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
canvas.drawCircle(center_x, center_y, dialFaceRadius, paint);
}
private void drawTicks(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.black));
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
for(int i = 0; i < tickTotalCount; i++) {
angle = (float) (((tickAngleEnd - tickAngleStart) * i / (tickTotalCount - 1) + tickAngleStart) / 180 * Math.PI);
x1 = (float) (center_x + Math.cos(angle) * tickEdgeRadius);
y1 = (float) (center_y - Math.sin(angle) * tickEdgeRadius);
if((i % (tickMinorCount + 1)) == 0) {
paint.setStrokeWidth(tickMajorStroke);
x2 = (float) (center_x + Math.cos(angle) * tickInnerMajorRadius);
y2 = (float) (center_y - Math.sin(angle) * tickInnerMajorRadius);
} else {
paint.setStrokeWidth(tickMinorStroke);
x2 = (float) (center_x + Math.cos(angle) * tickInnerMinorRadius);
y2 = (float) (center_y - Math.sin(angle) * tickInnerMinorRadius);
}
canvas.drawLine(x1, y1, x2, y2, paint);
}
}
private void drawNumeral(Canvas canvas) {
paint.reset();
paint.setTextSize(numberFontSize);
paint.setColor(getResources().getColor(android.R.color.black));
for(int i = 0; i < tickMajorCount; i++) {
angle = (float) (((tickAngleEnd - tickAngleStart) * i / (tickMajorCount - 1) + tickAngleStart) / 180 * Math.PI);
number = (numberMax - numberMin) * i / (tickMajorCount - 1) + numberMin;
str = String.valueOf(number);
paint.getTextBounds(str, 0, str.length(), rect);
double c = Math.cos(angle);
double s = Math.sin(angle);
if(rect.width() * Math.abs(s) < rect.height() * Math.abs(c)) {
x2 = (float) (Math.signum(c) * rect.width() / 2);
y2 = (float) (Math.tan(angle) * x2);
} else {
y2 = (float) (Math.signum(s) * rect.height() / 2);
x2 = (float) (1 / Math.tan(angle) * y2);//Math.cotg(angle) * y;
}
x1 = (float) (center_x + Math.cos(angle) * numberRadius - rect.width() / 2 - x2 * 1.25);
y1 = (float) (center_y - Math.sin(angle) * numberRadius + rect.height() / 2 + y2 * 1.25);
canvas.drawText(str, x1, y1, paint);
}
}
private void drawTitle(Canvas canvas) {
paint.reset();
paint.setTextSize(titleFontSize);
paint.setColor(getResources().getColor(android.R.color.black));
angle = (float) ((270.0 / 180) * Math.PI);
paint.getTextBounds(titleStr, 0, str.length(), rect);
x1 = (float) (center_x + Math.cos(angle) * titleRadius - rect.width() / 2);
y1 = (float) (center_y - Math.sin(angle) * titleRadius + rect.height() / 3);
canvas.drawText(titleStr, x1, y1, paint);
}
private void drawArrow(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.holo_red_dark));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
//Calculate (x,y) coordinates for the arrow path.
temp = (tickAngleEnd - tickAngleStart) * sensorInput / numberMax + tickAngleStart;
angle = (float) (temp / 180 * Math.PI);
x1 = (float) (center_x + Math.cos(angle) * arrowTipRadius);
y1 = (float) (center_y - Math.sin(angle) * arrowTipRadius);
angle = (float) ((temp + 170) / 180 * Math.PI);
x2 = (float) (center_x + Math.cos(angle) * arrowRearRadius);
y2 = (float) (center_y - Math.sin(angle) * arrowRearRadius);
angle = (float) ((temp - 170) / 180 * Math.PI);
x3 = (float) (center_x + Math.cos(angle) * arrowRearRadius);
y3 = (float) (center_y - Math.sin(angle) * arrowRearRadius);
//Draw arrow path using calculated coordinates.
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.close();
canvas.drawPath(path, paint);
//Calculate (x,y) coordinates for dial center.
x1 = center_x;
y1 = center_y;
canvas.drawCircle(x1, y1, arrowCenterRadius, paint);
paint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawCircle(x1, y1, arrowPinRadius, paint);
}
}
My MainActivity:
package net.dynu.kubie.redneksldhlr;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import org.w3c.dom.Text;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
hideSystemUI();
startGenerating();
}
#Override
protected void onResume() {
super.onResume();
hideSystemUI();
//demoData();
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
hideSystemUI();
//demoData();
}
}
private void hideSystemUI() {
// Enables regular immersive mode.
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN);
}
private void startGenerating() {
DoSomethingThread randomWork = new DoSomethingThread();
randomWork.start();
}
public class DoSomethingThread extends Thread {
private static final String TAG = "DoSomethingThread";
private static final int DELAY = 15000; // 5 seconds
private static final int RANDOM_MULTIPLIER = 120;
#Override
public void run() {
//Log.v(TAG, "doing work in Random Number Thread");
while (true) {
float randNum = (float) (Math.random() * RANDOM_MULTIPLIER);
// need to publish the random number back on the UI at this point in the code through the publishProgress(randNum) call
publishProgress(randNum);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
// Log.v(TAG, "Interrupting and stopping the Random Number Thread");
return;
}
}
}
}
private void publishProgress(float randNum) {
//Log.v(TAG, "reporting back from the Random Number Thread");
//final String text = String.format(getString(R.string.service_msg), randNum);
//final String text = Integer.toString(randNum);
final float text = randNum;
runOnUiThread(new Runnable() {
#Override
public void run() {
updateResults(text);
}
});
}
public void updateResults(float results) {
//TextView myTextView = (TextView) findViewById(R.id.testTextView);
DialGaugeView myDial = findViewById(R.id.my_gauge);
myDial.setSensorInput(results);
}
}
I eventually realized that my issue wasn't artifacts at all. My arrow is being drawn as a series of paths. If I do not reset the path prior to adding coordinates for the new arrow, the previous arrow(s) just get dragged along. Adding path.reset(); prior to the addition of the first arrow coordinate solved my problem.
//Draw arrow path using calculated coordinates.
path.reset();
path.moveTo(x1, y1);
path.moveTo(x2, y2);
path.moveTo(x3, y3);
path.close();
canvas.drawPath(path, paint);
I want to implement the Ringdroid waveform in my android app.But for some songs,the waveform created after choosing the song is larger than the screen size and for songs the waveform width is smaller than the mobile screen width .
Plz suggest me the change in code of Ringdroid ,so that every time the waveform of song totally covers the width of screen.
This is the link of Ringdroid project
https://github.com/google/ringdroid
After a whole lot of searching and surfing about it. I tried it myself and got the desired output with it
This is the WaveformView class of Ringdroid
package com.ringdroid;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import com.ringdroid.soundfile.SoundFile;
/**
* WaveformView is an Android view that displays a visual representation
* of an audio waveform. It retrieves the frame gains from a CheapSoundFile
* object and recomputes the shape contour at several zoom levels.
*
* This class doesn't handle selection or any of the touch interactions
* directly, so it exposes a listener interface. The class that embeds
* this view should add itself as a listener and make the view scroll
* and respond to other events appropriately.
*
* WaveformView doesn't actually handle selection, but it will just display
* the selected part of the waveform in a different color.
*/
public class WaveformView extends View {
public interface WaveformListener {
public void waveformTouchStart(float x);
public void waveformTouchMove(float x);
public void waveformTouchEnd();
public void waveformFling(float x);
public void waveformDraw();
public void waveformZoomIn();
public void waveformZoomOut();
};
// Colors
private Paint mGridPaint;
private Paint mSelectedLinePaint;
private Paint mUnselectedLinePaint;
private Paint mUnselectedBkgndLinePaint;
private Paint mBorderLinePaint;
private Paint mPlaybackLinePaint;
private Paint mTimecodePaint;
private SoundFile mSoundFile;
private int[] mLenByZoomLevel;
private double[][] mValuesByZoomLevel;
private double[] mZoomFactorByZoomLevel;
private int[] mHeightsAtThisZoomLevel;
private int mZoomLevel;
private int mNumZoomLevels;
private int mSampleRate;
private int mSamplesPerFrame;
private int mOffset;
private int mSelectionStart;
private int mSelectionEnd;
private int mPlaybackPos;
private float mDensity;
private float mInitialScaleSpan;
private WaveformListener mListener;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private boolean mInitialized;
public WaveformView(Context context, AttributeSet attrs) {
super(context, attrs);
// We don't want keys, the markers get these
setFocusable(false);
Resources res = getResources();
mGridPaint = new Paint();
mGridPaint.setAntiAlias(false);
mGridPaint.setColor(res.getColor(R.color.grid_line));
mSelectedLinePaint = new Paint();
mSelectedLinePaint.setAntiAlias(false);
mSelectedLinePaint.setColor(res.getColor(R.color.waveform_selected));
mUnselectedLinePaint = new Paint();
mUnselectedLinePaint.setAntiAlias(false);
mUnselectedLinePaint.setColor(res.getColor(R.color.waveform_unselected));
mUnselectedBkgndLinePaint = new Paint();
mUnselectedBkgndLinePaint.setAntiAlias(false);
mUnselectedBkgndLinePaint.setColor(res.getColor(R.color.waveform_unselected_bkgnd_overlay));
mBorderLinePaint = new Paint();
mBorderLinePaint.setAntiAlias(true);
mBorderLinePaint.setStrokeWidth(1.5f);
mBorderLinePaint.setPathEffect(new DashPathEffect(new float[] { 3.0f, 2.0f }, 0.0f));
mBorderLinePaint.setColor(res.getColor(R.color.selection_border));
mPlaybackLinePaint = new Paint();
mPlaybackLinePaint.setAntiAlias(false);
mPlaybackLinePaint.setColor(res.getColor(R.color.playback_indicator));
mTimecodePaint = new Paint();
mTimecodePaint.setTextSize(12);
mTimecodePaint.setAntiAlias(true);
mTimecodePaint.setColor(res.getColor(R.color.timecode));
mTimecodePaint.setShadowLayer(2, 1, 1, res.getColor(R.color.timecode_shadow));
mGestureDetector = new GestureDetector(
context,
new GestureDetector.SimpleOnGestureListener() {
public boolean onFling(MotionEvent e1, MotionEvent e2, float vx, float vy) {
mListener.waveformFling(vx);
return true;
}
}
);
mScaleGestureDetector = new ScaleGestureDetector(
context,
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
public boolean onScaleBegin(ScaleGestureDetector d) {
Log.v("Ringdroid", "ScaleBegin " + d.getCurrentSpanX());
mInitialScaleSpan = Math.abs(d.getCurrentSpanX());
return true;
}
public boolean onScale(ScaleGestureDetector d) {
float scale = Math.abs(d.getCurrentSpanX());
Log.v("Ringdroid", "Scale " + (scale - mInitialScaleSpan));
if (scale - mInitialScaleSpan > 40) {
mListener.waveformZoomIn();
mInitialScaleSpan = scale;
}
if (scale - mInitialScaleSpan < -40) {
mListener.waveformZoomOut();
mInitialScaleSpan = scale;
}
return true;
}
public void onScaleEnd(ScaleGestureDetector d) {
Log.v("Ringdroid", "ScaleEnd " + d.getCurrentSpanX());
}
}
);
mSoundFile = null;
mLenByZoomLevel = null;
mValuesByZoomLevel = null;
mHeightsAtThisZoomLevel = null;
mOffset = 0;
mPlaybackPos = -1;
mSelectionStart = 0;
mSelectionEnd = 0;
mDensity = 1.0f;
mInitialized = false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleGestureDetector.onTouchEvent(event);
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
mListener.waveformTouchStart(event.getX());
break;
case MotionEvent.ACTION_MOVE:
mListener.waveformTouchMove(event.getX());
break;
case MotionEvent.ACTION_UP:
mListener.waveformTouchEnd();
break;
}
return true;
}
public boolean hasSoundFile() {
return mSoundFile != null;
}
public void setSoundFile(SoundFile soundFile) {
mSoundFile = soundFile;
mSampleRate = mSoundFile.getSampleRate();
mSamplesPerFrame = mSoundFile.getSamplesPerFrame();
computeDoublesForAllZoomLevels();
mHeightsAtThisZoomLevel = null;
}
public boolean isInitialized() {
return mInitialized;
}
public int getZoomLevel() {
return mZoomLevel;
}
public void setZoomLevel(int zoomLevel) {
while (mZoomLevel > zoomLevel) {
zoomIn();
}
while (mZoomLevel < zoomLevel) {
zoomOut();
}
}
public boolean canZoomIn() {
return (mZoomLevel > 0);
}
public void zoomIn() {
if (canZoomIn()) {
mZoomLevel--;
mSelectionStart *= 2;
mSelectionEnd *= 2;
mHeightsAtThisZoomLevel = null;
int offsetCenter = mOffset + getMeasuredWidth() / 2;
offsetCenter *= 2;
mOffset = offsetCenter - getMeasuredWidth() / 2;
if (mOffset < 0)
mOffset = 0;
invalidate();
}
}
public boolean canZoomOut() {
return (mZoomLevel < mNumZoomLevels - 1);
}
public void zoomOut() {
if (canZoomOut()) {
mZoomLevel++;
mSelectionStart /= 2;
mSelectionEnd /= 2;
int offsetCenter = mOffset + getMeasuredWidth() / 2;
offsetCenter /= 2;
mOffset = offsetCenter - getMeasuredWidth() / 2;
if (mOffset < 0)
mOffset = 0;
mHeightsAtThisZoomLevel = null;
invalidate();
}
}
public int maxPos() {
return mLenByZoomLevel[mZoomLevel];
}
public int secondsToFrames(double seconds) {
return (int)(1.0 * seconds * mSampleRate / mSamplesPerFrame + 0.5);
}
public int secondsToPixels(double seconds) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)(z * seconds * mSampleRate / mSamplesPerFrame + 0.5);
}
public double pixelsToSeconds(int pixels) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (pixels * (double)mSamplesPerFrame / (mSampleRate * z));
}
public int millisecsToPixels(int msecs) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)((msecs * 1.0 * mSampleRate * z) /
(1000.0 * mSamplesPerFrame) + 0.5);
}
public int pixelsToMillisecs(int pixels) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)(pixels * (1000.0 * mSamplesPerFrame) /
(mSampleRate * z) + 0.5);
}
public void setParameters(int start, int end, int offset) {
mSelectionStart = start;
mSelectionEnd = end;
mOffset = offset;
}
public int getStart() {
return mSelectionStart;
}
public int getEnd() {
return mSelectionEnd;
}
public int getOffset() {
return mOffset;
}
public void setPlayback(int pos) {
mPlaybackPos = pos;
}
public void setListener(WaveformListener listener) {
mListener = listener;
}
public void recomputeHeights(float density) {
mHeightsAtThisZoomLevel = null;
mDensity = density;
mTimecodePaint.setTextSize((int)(12 * density));
invalidate();
}
protected void drawWaveformLine(Canvas canvas,
int x, int y0, int y1,
Paint paint) {
canvas.drawLine(x, y0, x, y1, paint);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mSoundFile == null)
return;
if (mHeightsAtThisZoomLevel == null)
computeIntsForThisZoomLevel();
// Draw waveform
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
int start = mOffset;
int width = mHeightsAtThisZoomLevel.length - start;
int ctr = measuredHeight / 2;
if (width > measuredWidth)
width = measuredWidth;
// Draw grid
double onePixelInSecs = pixelsToSeconds(1);
boolean onlyEveryFiveSecs = (onePixelInSecs > 1.0 / 50.0);
double fractionalSecs = mOffset * onePixelInSecs;
int integerSecs = (int) fractionalSecs;
int i = 0;
while (i < width) {
i++;
fractionalSecs += onePixelInSecs;
int integerSecsNew = (int) fractionalSecs;
if (integerSecsNew != integerSecs) {
integerSecs = integerSecsNew;
if (!onlyEveryFiveSecs || 0 == (integerSecs % 5)) {
canvas.drawLine(i, 0, i, measuredHeight, mGridPaint);
}
}
}
// Draw waveform
for (i = 0; i < width; i++) {
Paint paint;
if (i + start >= mSelectionStart &&
i + start < mSelectionEnd) {
paint = mSelectedLinePaint;
} else {
drawWaveformLine(canvas, i, 0, measuredHeight,
mUnselectedBkgndLinePaint);
paint = mUnselectedLinePaint;
}
drawWaveformLine(
canvas, i,
ctr - mHeightsAtThisZoomLevel[start + i],
ctr + 1 + mHeightsAtThisZoomLevel[start + i],
paint);
if (i + start == mPlaybackPos) {
canvas.drawLine(i, 0, i, measuredHeight, mPlaybackLinePaint);
}
}
// If we can see the right edge of the waveform, draw the
// non-waveform area to the right as unselected
for (i = width; i < measuredWidth; i++) {
drawWaveformLine(canvas, i, 0, measuredHeight,
mUnselectedBkgndLinePaint);
}
// Draw borders
canvas.drawLine(
mSelectionStart - mOffset + 0.5f, 30,
mSelectionStart - mOffset + 0.5f, measuredHeight,
mBorderLinePaint);
canvas.drawLine(
mSelectionEnd - mOffset + 0.5f, 0,
mSelectionEnd - mOffset + 0.5f, measuredHeight - 30,
mBorderLinePaint);
// Draw timecode
double timecodeIntervalSecs = 1.0;
if (timecodeIntervalSecs / onePixelInSecs < 50) {
timecodeIntervalSecs = 5.0;
}
if (timecodeIntervalSecs / onePixelInSecs < 50) {
timecodeIntervalSecs = 15.0;
}
// Draw grid
fractionalSecs = mOffset * onePixelInSecs;
int integerTimecode = (int) (fractionalSecs / timecodeIntervalSecs);
i = 0;
while (i < width) {
i++;
fractionalSecs += onePixelInSecs;
integerSecs = (int) fractionalSecs;
int integerTimecodeNew = (int) (fractionalSecs /
timecodeIntervalSecs);
if (integerTimecodeNew != integerTimecode) {
integerTimecode = integerTimecodeNew;
// Turn, e.g. 67 seconds into "1:07"
String timecodeMinutes = "" + (integerSecs / 60);
String timecodeSeconds = "" + (integerSecs % 60);
if ((integerSecs % 60) < 10) {
timecodeSeconds = "0" + timecodeSeconds;
}
String timecodeStr = timecodeMinutes + ":" + timecodeSeconds;
float offset = (float) (
0.5 * mTimecodePaint.measureText(timecodeStr));
canvas.drawText(timecodeStr,
i - offset,
(int)(12 * mDensity),
mTimecodePaint);
}
}
if (mListener != null) {
mListener.waveformDraw();
}
}
/**
* Called once when a new sound file is added
*/
private void computeDoublesForAllZoomLevels() {
int numFrames = mSoundFile.getNumFrames();
int[] frameGains = mSoundFile.getFrameGains();
double[] smoothedGains = new double[numFrames];
if (numFrames == 1) {
smoothedGains[0] = frameGains[0];
} else if (numFrames == 2) {
smoothedGains[0] = frameGains[0];
smoothedGains[1] = frameGains[1];
} else if (numFrames > 2) {
smoothedGains[0] = (double)(
(frameGains[0] / 2.0) +
(frameGains[1] / 2.0));
for (int i = 1; i < numFrames - 1; i++) {
smoothedGains[i] = (double)(
(frameGains[i - 1] / 3.0) +
(frameGains[i ] / 3.0) +
(frameGains[i + 1] / 3.0));
}
smoothedGains[numFrames - 1] = (double)(
(frameGains[numFrames - 2] / 2.0) +
(frameGains[numFrames - 1] / 2.0));
}
// Make sure the range is no more than 0 - 255
double maxGain = 1.0;
for (int i = 0; i < numFrames; i++) {
if (smoothedGains[i] > maxGain) {
maxGain = smoothedGains[i];
}
}
double scaleFactor = 1.0;
if (maxGain > 255.0) {
scaleFactor = 255 / maxGain;
}
// Build histogram of 256 bins and figure out the new scaled max
maxGain = 0;
int gainHist[] = new int[256];
for (int i = 0; i < numFrames; i++) {
int smoothedGain = (int)(smoothedGains[i] * scaleFactor);
if (smoothedGain < 0)
smoothedGain = 0;
if (smoothedGain > 255)
smoothedGain = 255;
if (smoothedGain > maxGain)
maxGain = smoothedGain;
gainHist[smoothedGain]++;
}
// Re-calibrate the min to be 5%
double minGain = 0;
int sum = 0;
while (minGain < 255 && sum < numFrames / 20) {
sum += gainHist[(int)minGain];
minGain++;
}
// Re-calibrate the max to be 99%
sum = 0;
while (maxGain > 2 && sum < numFrames / 100) {
sum += gainHist[(int)maxGain];
maxGain--;
}
// Compute the heights
double[] heights = new double[numFrames];
double range = maxGain - minGain;
for (int i = 0; i < numFrames; i++) {
double value = (smoothedGains[i] * scaleFactor - minGain) / range;
if (value < 0.0)
value = 0.0;
if (value > 1.0)
value = 1.0;
heights[i] = value * value;
}
mNumZoomLevels = 5;
mLenByZoomLevel = new int[5];
mZoomFactorByZoomLevel = new double[5];
mValuesByZoomLevel = new double[5][];
// Level 0 is doubled, with interpolated values
mLenByZoomLevel[0] = numFrames * 2;
mZoomFactorByZoomLevel[0] = 2.0;
mValuesByZoomLevel[0] = new double[mLenByZoomLevel[0]];
if (numFrames > 0) {
mValuesByZoomLevel[0][0] = 0.5 * heights[0];
mValuesByZoomLevel[0][1] = heights[0];
}
for (int i = 1; i < numFrames; i++) {
mValuesByZoomLevel[0][2 * i] = 0.5 * (heights[i - 1] + heights[i]);
mValuesByZoomLevel[0][2 * i + 1] = heights[i];
}
// Level 1 is normal
mLenByZoomLevel[1] = numFrames;
mValuesByZoomLevel[1] = new double[mLenByZoomLevel[1]];
mZoomFactorByZoomLevel[1] = 1.0;
for (int i = 0; i < mLenByZoomLevel[1]; i++) {
mValuesByZoomLevel[1][i] = heights[i];
}
// 3 more levels are each halved
for (int j = 2; j < 5; j++) {
mLenByZoomLevel[j] = mLenByZoomLevel[j - 1] / 2;
mValuesByZoomLevel[j] = new double[mLenByZoomLevel[j]];
mZoomFactorByZoomLevel[j] = mZoomFactorByZoomLevel[j - 1] / 2.0;
for (int i = 0; i < mLenByZoomLevel[j]; i++) {
mValuesByZoomLevel[j][i] =
0.5 * (mValuesByZoomLevel[j - 1][2 * i] +
mValuesByZoomLevel[j - 1][2 * i + 1]);
}
}
if (numFrames > 5000) {
mZoomLevel = 3;
} else if (numFrames > 1000) {
mZoomLevel = 2;
} else if (numFrames > 300) {
mZoomLevel = 1;
} else {
mZoomLevel = 0;
}
mInitialized = true;
}
/**
* Called the first time we need to draw when the zoom level has changed
* or the screen is resized
*/
private void computeIntsForThisZoomLevel() {
int halfHeight = (getMeasuredHeight() / 2) - 1;
mHeightsAtThisZoomLevel = new int[mLenByZoomLevel[mZoomLevel]];
for (int i = 0; i < mLenByZoomLevel[mZoomLevel]; i++) {
mHeightsAtThisZoomLevel[i] =
(int)(mValuesByZoomLevel[mZoomLevel][i] * halfHeight);
}
}
}
The change is here in this part of code
DisplayMetrics displaymetrics = getContext().getResources().getDisplayMetrics();
int ScreenWidth= displaymetrics.widthPixels;
// Draw waveform
for ( i = 0; i < width; i++) {
Paint paint;
if (i + start >= mSelectionStart &&
i + start < mSelectionEnd) {
paint = mSelectedLinePaint;
} else {
drawWaveformLine(canvas, ((ScreenWidth/width)*i), 0, measuredHeight,
mUnselectedBkgndLinePaint);
paint = mUnselectedLinePaint;
}
drawWaveformLine(
canvas, ((ScreenWidth/width)*i),
ctr - mHeightsAtThisZoomLevel[start + i],
ctr + 1 + mHeightsAtThisZoomLevel[start + i],
paint);
you have to change the x-axis of draw line method according to your screen size
import java.util.LinkedList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceView;
/**
* A view that displays audio data on the screen as a waveform.
*/
public class WaveformView extends SurfaceView {
// The number of buffer frames to keep around (for a nice fade-out
// visualization.
private static final int HISTORY_SIZE = 6;
// To make quieter sounds still show up well on the display, we use
// +/- 8192 as the amplitude that reaches the top/bottom of the view
// instead of +/- 32767. Any samples that have magnitude higher than this
// limit will simply be clipped during drawing.
private static final float MAX_AMPLITUDE_TO_DRAW = 8192.0f;
// The queue that will hold historical audio data.
private LinkedList<short[]> mAudioData;
private Paint mPaint;
public WaveformView(Context context) {
this(context, null, 0);
}
public WaveformView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveformView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(0);
mPaint.setAntiAlias(true);
}
/**
* Updates the waveform view with a new "frame" of samples and renders it.
* The new frame gets added to the front of the rendering queue, pushing the
* previous frames back, causing them to be faded out visually.
*
* #param buffer the most recent buffer of audio samples.
*/
public synchronized void updateAudioData(short[] buffer) {
short[] newBuffer;
// We want to keep a small amount of history in the view to provide a nice
// fading effect. We use a linked list that we treat as a queue for this.
if (mAudioData.size() == HISTORY_SIZE) {
newBuffer = mAudioData.removeFirst();
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
} else {
newBuffer = buffer.clone();
}
mAudioData.addLast(newBuffer);
// Update the display.
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
drawWaveform(canvas);
getHolder().unlockCanvasAndPost(canvas);
}
}
/**
* Repaints the view's surface.
*
* #param canvas the {#link Canvas} object on which to draw.
*/
private void drawWaveform(Canvas canvas) {
// Clear the screen each time because SurfaceView won't do this for us.
canvas.drawColor(Color.BLACK);
float width = getWidth();
float height = getHeight();
float centerY = height / 2;
// We draw the history from oldest to newest so that the older audio
// data is further back and darker than the most recent data.
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
for (short[] buffer : mAudioData) {
mPaint.setColor(Color.argb(brightness, 128, 255, 192));
float lastX = -1;
float lastY = -1;
// For efficiency, we don't draw all of the samples in the buffer,
// but only the ones that align with pixel boundaries.
for (int x = 0; x < width; x++) {
int index = (int) ((x / width) * buffer.length);
short sample = buffer[index];
float y = (sample / MAX_AMPLITUDE_TO_DRAW) * centerY + centerY;
if (lastX != -1) {
canvas.drawLine(lastX, lastY, x, y, mPaint);
}
lastX = x;
lastY = y;
}
brightness += colorDelta;
}
}
}
I want implement move a sprite from position (x ,y ) to position action_down (x1 , y1) .But I can't rotate it .Please help me .Thanks
This is my code:
public Sprite(GameView gameView, Bitmap bmp) {
this.gameView = gameView;
this.bmp = bmp;
this.width = bmp.getWidth() / BMP_COLUMNS;// create width, height
this.height = bmp.getHeight() / BMP_ROWS;
Random rnd = new Random(System.currentTimeMillis());
x = rnd.nextInt(gameView.getWidth() - bmp.getWidth());
y = rnd.nextInt(gameView.getHeight() - bmp.getHeight());
}
public void onDraw(Canvas canvas) {
Matrix matrix = new Matrix();
matrix.postTranslate(x, y);
float dx = x1-x;
float dy = y1-y;
float d = (float)Math.sqrt(dx*dx+dy*dy);
vx = (float) (dx*5/d)/3 ;
vy = (float) (dy*5/d)/3 ;
if(k==1){
x += vx ;
y += vy ;
}
currentFrame = ++currentFrame % BMP_COLUMNS;
int srcX = currentFrame * width;
int srcY = 0 * height;
Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
Rect dst = new Rect(x, y, x + width, y + height);
canvas.drawBitmap(bmp, src, dst, null);
}
You should look at matrix.postRotate or canvas.rotate.
Here you go:
Note: you need to convert from Bitmap to Image.
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
/**
* Created by Chris on 3/28/2014.
*/
public class Sprite {
private Image i;
public Sprite(Image image) {
this.i = image;
}
private BufferedImage image = null;
private Graphics2D graphics = null;
public void onDraw(Canvas canvas) {
if(image == null || graphics == null) {
setup();
}
Graphics g = canvas.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
//Where to draw the Sprite on the canvas.
int x = 100;
int y = 100;
//Because graphics is an instance of Graphics2D
//Converts the degrees "45" to radians.
double rotationAngle = Math.toRadians(45);
double locX = image.getWidth() / 2;
double locY = image.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationAngle, locX, locY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
graphics.drawImage(op.filter(image, null), 0, 0, null);
g.drawImage(image, x, y, (int) (image.getWidth() / 2), (int) (image.getHeight() / 2), null);
}
/**
* Sets the Image up.
*/
private void setup() {
if(image != null) {
image.flush();
image = null;
}
if(graphics != null) {
graphics.dispose();
graphics = null;
}
image = new BufferedImage(i.getWidth(null) * 2, i.getHeight(null) * 2, BufferedImage.TYPE_INT_ARGB);
graphics = image.createGraphics();
}
}
I would like to be able to use the same drawable to represent both:
and
as the same drawable, and re-color the drawable based on some programmatic values, so that the end user could re-theme the interface.
What is the best way to do this? I have tried (and reused the icons from) this previous S.O. question but I can't represent the change as a simple change of hue, since it also varies in saturation and value..
Is it best to store the icon as all white in the area I want changed? or transparent? or some other solid color?
Is there some method that allows you to figure out the matrix based on the difference between Color of red_icon and Color of blue_icon?
So after a lot of trial and error, reading different articles, and most importantly, going through the API Demos (ColorFilters.java -- found in com.example.android.apis.graphics) I found the solution.
For solid images, I have found it is best to use the color filter PorterDuff.Mode.SRC_ATOP because it will overlay the color on top of the source image, allowing you to change the color to the exact color you are looking for.
For images that are more complex, like the one above, I have found the best thing to do is to color the entire image WHITE (FFFFFF) so that when you do PorterDuff.Mode.MULTIPLY, you end up with the correct colors, and all of the black (000000) in your image will remain black.
The colorfilters.java shows you how it's done if your drawing on a canvas, but if all you need is to color a drawable then this will work:
COLOR2 = Color.parseColor("#FF"+getColor());
Mode mMode = Mode.SRC_ATOP;
Drawable d = mCtx.getResources().getDrawable(R.drawable.image);
d.setColorFilter(COLOR2,mMode)
I created a demo activity using some of the API Demo code to swap between every color filter mode to try them out for different situations and have found it to be invaluable, so I thought I would post it here.
public class ColorFilters extends GraphicsActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private Activity mActivity;
private Drawable mDrawable;
private Drawable[] mDrawables;
private Paint mPaint;
private Paint mPaint2;
private float mPaintTextOffset;
private int[] mColors;
private PorterDuff.Mode[] mModes;
private int mModeIndex;
private Typeface futura_bold;
private AssetManager assets;
private static void addToTheRight(Drawable curr, Drawable prev) {
Rect r = prev.getBounds();
int x = r.right + 12;
int center = (r.top + r.bottom) >> 1;
int h = curr.getIntrinsicHeight();
int y = center - (h >> 1);
curr.setBounds(x, y, x + curr.getIntrinsicWidth(), y + h);
}
public SampleView(Activity activity) {
super(activity);
mActivity = activity;
Context context = activity;
setFocusable(true);
/**1. GET DRAWABLE, SET BOUNDS */
assets = context.getAssets();
mDrawable = context.getResources().getDrawable(R.drawable.roundrect_gray_button_bg_nine);
mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
mDrawable.setDither(true);
int[] resIDs = new int[] {
R.drawable.roundrect_gray_button_bg,
R.drawable.order_button_white,
R.drawable.yellowbar
};
mDrawables = new Drawable[resIDs.length];
Drawable prev = mDrawable;
for (int i = 0; i < resIDs.length; i++) {
mDrawables[i] = context.getResources().getDrawable(resIDs[i]);
mDrawables[i].setDither(true);
addToTheRight(mDrawables[i], prev);
prev = mDrawables[i];
}
/**2. SET Paint for writing text on buttons */
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(16);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint2 = new Paint(mPaint);
/** Calculating size based on font */
futura_bold = Typeface.createFromAsset(assets,
"fonts/futurastd-bold.otf");
//Determine size and offset to write text in label based on font size.
mPaint.setTypeface(futura_bold);
Paint.FontMetrics fm = mPaint.getFontMetrics();
mPaintTextOffset = (fm.descent + fm.ascent) * 0.5f;
mColors = new int[] {
0,
0xFFA60017,//WE USE THESE
0xFFC6D405,
0xFF4B5B98,
0xFF656565,
0xFF8888FF,
0xFF4444FF,
};
mModes = new PorterDuff.Mode[] {
PorterDuff.Mode.DARKEN,
PorterDuff.Mode.DST,
PorterDuff.Mode.DST_ATOP,
PorterDuff.Mode.DST_IN,
PorterDuff.Mode.DST_OUT,
PorterDuff.Mode.DST_OVER,
PorterDuff.Mode.LIGHTEN,
PorterDuff.Mode.MULTIPLY,
PorterDuff.Mode.SCREEN,
PorterDuff.Mode.SRC,
PorterDuff.Mode.SRC_ATOP,
PorterDuff.Mode.SRC_IN,
PorterDuff.Mode.SRC_OUT,
PorterDuff.Mode.SRC_OVER,
PorterDuff.Mode.XOR
};
mModeIndex = 0;
updateTitle();
}
private void swapPaintColors() {
if (mPaint.getColor() == 0xFF000000) {
mPaint.setColor(0xFFFFFFFF);
mPaint2.setColor(0xFF000000);
} else {
mPaint.setColor(0xFF000000);
mPaint2.setColor(0xFFFFFFFF);
}
mPaint2.setAlpha(0);
}
private void updateTitle() {
mActivity.setTitle(mModes[mModeIndex].toString());
}
private void drawSample(Canvas canvas, ColorFilter filter) {
/** Create a rect around bounds, ensure size offset */
Rect r = mDrawable.getBounds();
float x = (r.left + r.right) * 0.5f;
float y = (r.top + r.bottom) * 0.5f - mPaintTextOffset;
/**Set color filter to selected color
* create canvas (filled with this color)
* Write text using paint (new color)
*/
mDrawable.setColorFilter(filter);
mDrawable.draw(canvas);
/** If the text doesn't fit in the button, make the text size smaller until it does*/
final float size = mPaint.measureText("Label");
if((int) size > (r.right-r.left)) {
float ts = mPaint.getTextSize();
Log.w("DEBUG","Text size was"+ts);
mPaint.setTextSize(ts-2);
}
canvas.drawText("Sausage Burrito", x, y, mPaint);
/** Write the text and draw it onto the drawable*/
for (Drawable dr : mDrawables) {
dr.setColorFilter(filter);
dr.draw(canvas);
}
}
#Override protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFCCCCCC);
canvas.translate(8, 12);
for (int color : mColors) {
ColorFilter filter;
if (color == 0) {
filter = null;
} else {
filter = new PorterDuffColorFilter(color,
mModes[mModeIndex]);
}
drawSample(canvas, filter);
canvas.translate(0, 55);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
// update mode every other time we change paint colors
if (mPaint.getColor() == 0xFFFFFFFF) {
mModeIndex = (mModeIndex + 1) % mModes.length;
updateTitle();
}
swapPaintColors();
invalidate();
break;
}
return true;
}
}
}
The two other dependencies, GraphicsActivity.java and PictureLayout.java, can be copied directly from the API Demos activity if you would like to test it out.
This is really easy to do on Lollipop. Make an xml drawable and reference your png and set the tint like this:
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/ic_back"
android:tint="#color/red_tint"/>
Your answer is very nice. Although, this solution is practice too if you're using a Textview and a embed drawable:
int colorARGB = R.color.your_color;
Drawable[] textviewDrawables = drawerItem.getCompoundDrawables();
// Left Drawable
textviewDrawables[0].setColorFilter(colorARGB, PorterDuff.Mode.SRC_ATOP);
This is what i did after looking into documentation
public PorterDuffColorFilter getDrawableFilter(){
return new PorterDuffColorFilter(ContextCompat.getColor(this, R.color.color_black), PorterDuff.Mode.SRC_ATOP);
}
and called it
yourdrawable.setColorFilter(getDrawableFilter());
In case if you want to apply color filter to your image in ImageView you can implement it even in easier way. Just use attribute android:tint in ImageView in xml.
Example:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/your_drawable"
android:tint="#color/your_color" />
Tested on Android 4.1.2 and 6.0.1
Here is something better, IMHO, than the accepted answer. It is derived from this StackOverflow thread: Understanding the Use of ColorMatrix and ColorMatrixColorFilter to Modify a Drawable's Hue
Example usage:
ImageView imageView = ...;
Drawable drawable = imageView.getDrawable();
ColorFilter colorFilter = ColorFilterGenerator.from(drawable).to(Color.RED);
imageView.setColorFilter(colorFilter);
Copy the class into your project:
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PictureDrawable;
import android.widget.ImageView;
/**
* Creates a {#link ColorMatrixColorFilter} to adjust the hue, saturation, brightness, or
* contrast of an {#link Bitmap}, {#link Drawable}, or {#link ImageView}.
* <p/>
* Example usage:
* <br/>
* {#code imageView.setColorFilter(ColorFilterGenerator.from(Color.BLUE).to(Color.RED));}
*
* #author Jared Rummler <jared.rummler#gmail.com>
*/
public class ColorFilterGenerator {
// Based off answer from StackOverflow
// See: https://stackoverflow.com/a/15119089/1048340
private ColorFilterGenerator() {
throw new AssertionError();
}
public static From from(Drawable drawable) {
return new From(drawableToBitmap(drawable));
}
public static From from(Bitmap bitmap) {
return new From(bitmap);
}
public static From from(int color) {
return new From(color);
}
// --------------------------------------------------------------------------------------------
private static final double DELTA_INDEX[] = {
0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, 0.12, 0.14, 0.15, 0.16, 0.17, 0.18,
0.20, 0.21, 0.22, 0.24, 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, 0.44,
0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89,
0.92, 0.95, 0.98, 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, 1.60, 1.66, 1.72,
1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25, 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6,
3.8, 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4,
9.6, 9.8, 10.0
};
public static void adjustHue(ColorMatrix cm, float value) {
value = cleanValue(value, 180f) / 180f * (float) Math.PI;
if (value == 0) {
return;
}
float cosVal = (float) Math.cos(value);
float sinVal = (float) Math.sin(value);
float lumR = 0.213f;
float lumG = 0.715f;
float lumB = 0.072f;
float[] mat = new float[]{
lumR + cosVal * (1 - lumR) + sinVal * (-lumR),
lumG + cosVal * (-lumG) + sinVal * (-lumG),
lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0,
lumR + cosVal * (-lumR) + sinVal * (0.143f),
lumG + cosVal * (1 - lumG) + sinVal * (0.140f),
lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0,
lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)),
lumG + cosVal * (-lumG) + sinVal * (lumG),
lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f,
0f, 1f
};
cm.postConcat(new ColorMatrix(mat));
}
public static void adjustBrightness(ColorMatrix cm, float value) {
value = cleanValue(value, 100);
if (value == 0) {
return;
}
float[] mat = new float[]{
1, 0, 0, 0, value, 0, 1, 0, 0, value, 0, 0, 1, 0, value, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1
};
cm.postConcat(new ColorMatrix(mat));
}
public static void adjustContrast(ColorMatrix cm, int value) {
value = (int) cleanValue(value, 100);
if (value == 0) {
return;
}
float x;
if (value < 0) {
x = 127 + value / 100 * 127;
} else {
x = value % 1;
if (x == 0) {
x = (float) DELTA_INDEX[value];
} else {
x = (float) DELTA_INDEX[(value << 0)] * (1 - x)
+ (float) DELTA_INDEX[(value << 0) + 1] * x;
}
x = x * 127 + 127;
}
float[] mat = new float[]{
x / 127, 0, 0, 0, 0.5f * (127 - x), 0, x / 127, 0, 0, 0.5f * (127 - x), 0, 0,
x / 127, 0, 0.5f * (127 - x), 0, 0, 0, 1, 0, 0, 0, 0, 0, 1
};
cm.postConcat(new ColorMatrix(mat));
}
public static void adjustSaturation(ColorMatrix cm, float value) {
value = cleanValue(value, 100);
if (value == 0) {
return;
}
float x = 1 + ((value > 0) ? 3 * value / 100 : value / 100);
float lumR = 0.3086f;
float lumG = 0.6094f;
float lumB = 0.0820f;
float[] mat = new float[]{
lumR * (1 - x) + x, lumG * (1 - x), lumB * (1 - x), 0, 0, lumR * (1 - x),
lumG * (1 - x) + x, lumB * (1 - x), 0, 0, lumR * (1 - x), lumG * (1 - x),
lumB * (1 - x) + x, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1
};
cm.postConcat(new ColorMatrix(mat));
}
// --------------------------------------------------------------------------------------------
private static float cleanValue(float p_val, float p_limit) {
return Math.min(p_limit, Math.max(-p_limit, p_val));
}
private static float[] getHsv(int color) {
float[] hsv = new float[3];
Color.RGBToHSV(Color.red(color), Color.green(color), Color.blue(color), hsv);
return hsv;
}
/**
* Converts a {#link Drawable} to a {#link Bitmap}
*
* #param drawable
* The {#link Drawable} to convert
* #return The converted {#link Bitmap}.
*/
private static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
} else if (drawable instanceof PictureDrawable) {
PictureDrawable pictureDrawable = (PictureDrawable) drawable;
Bitmap bitmap = Bitmap.createBitmap(pictureDrawable.getIntrinsicWidth(),
pictureDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawPicture(pictureDrawable.getPicture());
return bitmap;
}
int width = drawable.getIntrinsicWidth();
width = width > 0 ? width : 1;
int height = drawable.getIntrinsicHeight();
height = height > 0 ? height : 1;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
/**
* Calculate the average red, green, blue color values of a bitmap
*
* #param bitmap
* a {#link Bitmap}
* #return
*/
private static int[] getAverageColorRGB(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int size = width * height;
int[] pixels = new int[size];
int r, g, b;
r = g = b = 0;
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
for (int i = 0; i < size; i++) {
int pixelColor = pixels[i];
if (pixelColor == Color.TRANSPARENT) {
size--;
continue;
}
r += Color.red(pixelColor);
g += Color.green(pixelColor);
b += Color.blue(pixelColor);
}
r /= size;
g /= size;
b /= size;
return new int[]{
r, g, b
};
}
/**
* Calculate the average color value of a bitmap
*
* #param bitmap
* a {#link Bitmap}
* #return
*/
private static int getAverageColor(Bitmap bitmap) {
int[] rgb = getAverageColorRGB(bitmap);
return Color.argb(255, rgb[0], rgb[1], rgb[2]);
}
// Builder
// --------------------------------------------------------------------------------------------
public static final class Builder {
int hue;
int contrast;
int brightness;
int saturation;
public Builder setHue(int hue) {
this.hue = hue;
return this;
}
public Builder setContrast(int contrast) {
this.contrast = contrast;
return this;
}
public Builder setBrightness(int brightness) {
this.brightness = brightness;
return this;
}
public Builder setSaturation(int saturation) {
this.saturation = saturation;
return this;
}
public ColorFilter build() {
ColorMatrix cm = new ColorMatrix();
adjustHue(cm, hue);
adjustContrast(cm, contrast);
adjustBrightness(cm, brightness);
adjustSaturation(cm, saturation);
return new ColorMatrixColorFilter(cm);
}
}
public static final class From {
final int oldColor;
private From(Bitmap bitmap) {
oldColor = getAverageColor(bitmap);
}
private From(int oldColor) {
this.oldColor = oldColor;
}
public ColorFilter to(int newColor) {
float[] hsv1 = getHsv(oldColor);
float[] hsv2 = getHsv(newColor);
int hue = (int) (hsv2[0] - hsv1[0]);
int saturation = (int) (hsv2[1] - hsv1[1]);
int brightness = (int) (hsv2[2] - hsv1[2]);
return new ColorFilterGenerator.Builder()
.setHue(hue)
.setSaturation(saturation)
.setBrightness(brightness)
.build();
}
}
}