I learned about how to create an OTP from drawable using canvas. OTP screen
But I want to have a box instead of a plane lines at the bottom, and I want to have the text in the middle of the box, instead of 8 dp above the line. I made a few modification like adding a rectf and drawing using canvas. It was easy. But It will not be responsive on all the screens.
#Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
int availableWidth = getWidth() - getPaddingRight() - getPaddingLeft();
int availableHeight= getHeight()-getPaddingTop()-getPaddingBottom();
if (mSpace < 0) {
mCharSize = (availableWidth / (mNumChars * 2 - 1));
} else {
mCharSize = (availableWidth - (mSpace * (mNumChars - 1))) / mNumChars;
}
int startX = getPaddingLeft();
int bottom = getHeight() - getPaddingBottom();
//Text Width
Editable text = getText();
int textLength = text.length();
float[] textWidths = new float[textLength];
getPaint().getTextWidths(getText(), 0, textLength, textWidths);
Paint mPaint = new Paint();
RectF rectF;
int cornerRadius = 15;
for (int i = 0; i < mNumChars; i++) {
mPaint.setColor(getResources().getColor(R.color.colorViews));
mPaint.setAlpha(70);
mPaint.setStyle(Paint.Style.FILL);
rectF = new RectF(startX, availableHeight, startX + (int) mCharSize, bottom);
canvas.drawRoundRect(rectF,cornerRadius,cornerRadius,mPaint);
// canvas.drawLine(startX, bottom, startX + mCharSize, bottom, mPaint);
if (getText().length() > i) {
float middle = startX + mCharSize / 2;
//y= bottom-mLineSpacing
canvas.drawText(text, i, i + 1, middle - textWidths[0] / 2, bottom-mLineSpacing, getPaint());
}
if (mSpace < 0) {
startX += mCharSize * 2;
} else {
startX += mCharSize + mSpace;
}
}
}
And my xml file looks something like this
<com.example.android.EditTextOTP
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:cursorVisible="false"
android:digits="1234567890"
android:inputType="number"
android:maxLength="4"
android:textIsSelectable="false"
android:textSize="20sp"
android:background="#null"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:paddingTop="60dp"
app:layout_constraintTop_toBottomOf="#id/tv_otp_text"
app:layout_constraintStart_toStartOf="#id/gl_inside_bg"
app:layout_constraintEnd_toEndOf="#id/gl_vertical_end"
android:id="#+id/et_otp_box"
>
</com.example.android.EditTextOTP>
The above way works fine but I want to place the number as I type in the center of the BOX. not roughly on the top of the BOX. How can I improve this?
Related
dear community, I'm facing the following problem, I'm creating a bar chart using the MP android Chart library available here: https://github.com/PhilJay/MPAndroidChart.
I'd like to set a gradient background for my bars, using the following code I was able to set the gradient background and get the following result.
val barDataSet = BarDataSet(dummyYValues, "DataSet 1")
barDataSet.axisDependency = YAxis.AxisDependency.RIGHT
val startColor = ContextCompat.getColor(context!!, R.color.top_graph)
val endColor = ContextCompat.getColor(context!!, R.color.bottom_graph)
val gradientColors: MutableList<GradientColor> = ArrayList()
gradientColors.add(GradientColor(endColor, startColor))
barDataSet.gradientColors = gradientColors
dataSets.add(barDataSet)
val finalData = BarData(dataSets)
finalData.barWidth =(0.3f)
And I'm trying to archive something like the following example
If you see the main difference is that the expected behavior displaying the gradient color in a uniform way, it means that it displays the color according to the graph not according to the values.
also, if anyone knows how to add the round corner at the top of the bars that would be helpful too.
Thanks.
I don't know the best solution but I copied the BarChart renderer class as:
public class RoundedBarChartRenderer extends BarLineScatterCandleBubbleRenderer{
protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
//Use this to replace the line c.drawRect(.....)
RectF rectF = new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3]);
c.drawRoundRect(rectF,15f,15f, mRenderPaint);
}}
Use the above class like this:
chart.renderer = RoundedBarChartRenderer(
chart,
chart.animator,
chart.viewPortHandler
)
To create a gradient background for a bar chart, use the code below.
barDataSet.setGradientColor(Color.parseColor("#00FF5722"),Color.parseColor("#FFFF5722");
Then use this to curve the corners of the bar chart. I got this from another source and it works perfectly. Here is the code.
CustomBarChartRender barChartRender = new CustomBarChartRender(barchart,barchart.getAnimator(), barchart.getViewPortHandler());
barChartRender.setRadius(12);
barchart.setRenderer(barChartRender);
create a new class CustomBarChartRender and add this,
public class CustomBarChartRender extends BarChartRenderer {
private final RectF mBarShadowRectBuffer = new RectF();
private int mRadius;
public CustomBarChartRender(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
public void setRadius(int mRadius) {
this.mRadius = mRadius;
}
protected void drawDataSet(Canvas c, #NotNull IBarDataSet dataSet, int index) {
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mBarBorderPaint.setColor(dataSet.getBarBorderColor());
mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth()));
mShadowPaint.setColor(dataSet.getBarShadowColor());
boolean drawBorder = dataSet.getBarBorderWidth() > 0f;
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
if (mChart.isDrawBarShadowEnabled()) {
mShadowPaint.setColor(dataSet.getBarShadowColor());
BarData barData = mChart.getBarData();
float barWidth = barData.getBarWidth();
float barWidthHalf = barWidth / 2.0f;
float x;
int i = 0;
double count = Math.min(Math.ceil((int) (double) ((float) dataSet.getEntryCount() * phaseX)), dataSet.getEntryCount());
while (i < count) {
BarEntry e = dataSet.getEntryForIndex(i);
x = e.getX();
mBarShadowRectBuffer.left = x - barWidthHalf;
mBarShadowRectBuffer.right = x + barWidthHalf;
trans.rectValueToPixel(mBarShadowRectBuffer);
if (!mViewPortHandler.isInBoundsLeft(mBarShadowRectBuffer.right)) {
i++;
continue;
}
if (!mViewPortHandler.isInBoundsRight(mBarShadowRectBuffer.left))
break;
mBarShadowRectBuffer.top = mViewPortHandler.contentTop();
mBarShadowRectBuffer.bottom = mViewPortHandler.contentBottom();
c.drawRoundRect(mBarRect, mRadius, mRadius, mShadowPaint);
/*if (mRadius > 0)
c.drawRoundRect(mBarRect, mRadius, mRadius, mHighlightPaint);
else
c.drawRect(mBarRect, mHighlightPaint);*/
i++;
}
}
// initialize the buffer
BarBuffer buffer = mBarBuffers[index];
buffer.setPhases(phaseX, phaseY);
buffer.setDataSet(index);
buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
buffer.setBarWidth(mChart.getBarData().getBarWidth());
buffer.feed(dataSet);
trans.pointValuesToPixel(buffer.buffer);
boolean isSingleColor = dataSet.getColors().size() == 1;
if (isSingleColor) {
mRenderPaint.setColor(dataSet.getColor());
}
int j = 0;
while (j < buffer.size()) {
if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
j += 4;
continue;
}
if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j]))
break;
if (!isSingleColor) {
// Set the color for the currently drawn value. If the index
// is out of bounds, reuse colors.
mRenderPaint.setColor(dataSet.getColor(j / 4));
}
if (dataSet.getGradientColor() != null) {
GradientColor gradientColor = dataSet.getGradientColor();
mRenderPaint.setShader(new LinearGradient(
buffer.buffer[j],
buffer.buffer[j + 3],
buffer.buffer[j],
buffer.buffer[j + 1],
gradientColor.getStartColor(),
gradientColor.getEndColor(),
android.graphics.Shader.TileMode.MIRROR));
}
if (dataSet.getGradientColors() != null) {
mRenderPaint.setShader(new LinearGradient(
buffer.buffer[j],
buffer.buffer[j + 3],
buffer.buffer[j],
buffer.buffer[j + 1],
dataSet.getGradientColor(j / 4).getStartColor(),
dataSet.getGradientColor(j / 4).getEndColor(),
Shader.TileMode.MIRROR));
}
Path path2 = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3]), mRadius, mRadius);
c.drawPath(path2, mRenderPaint);
if (drawBorder) {
Path path = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3]), mRadius, mRadius);
c.drawPath(path, mBarBorderPaint);
}
j += 4;
}
}
#NotNull
private Path roundRect(#NotNull RectF rect, float rx, float ry) {
float top = rect.top;
float left = rect.left;
float right = rect.right;
float bottom = rect.bottom;
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width / 2) rx = width / 2;
if (ry > height / 2) ry = height / 2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
path.rLineTo(-widthMinusCorners, 0);
path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
path.rLineTo(0, heightMinusCorners);
path.rLineTo(0, ry);
path.rLineTo(rx, 0);
path.rLineTo(widthMinusCorners, 0);
path.rLineTo(rx, 0);
path.rLineTo(0, -ry);
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}}
So I created a game where u can move your character and u need to pass lines with that character, but I have 1 problem. When the first line reaches bottom of my screen only then the score starts counting. I want to make so that when my character passes the lines gap the score would +1. What should I do?
EDIT: Also the score counts when last line reaches bottom!
if (obstacles.get(obstacles.size()-1).getRectangle().top >= Constants.SCREEN_HEIGHT ){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(0, new Obstacle(obstacleHeight, color, xStart, obstacles.get(0).getRectangle().top - obstacleHeight - obstacleGap, playerGap));
obstacles.remove(obstacles.size()-1);
score++;
}
}
public void draw(Canvas canvas){
for(Obstacle ob : obstacles)
ob.draw(canvas);
Paint paint = new Paint();
paint.setTextSize(100);
paint.setColor(Color.GREEN);
canvas.drawText("" + score, 50, 100 + paint.descent()- paint.descent(), paint);
EDIT:
int currY = -5*Constants.SCREEN_HEIGHT/4, rect;
while(currY < 0){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(new Obstacle(obstacleHeight, color, xStart, currY, playerGap));
currY += obstacleHeight + obstacleGap;
}
if (obstacles.get(obstacles.size() - 1).getRectangle().top >= currY) {
score++;
}
EDIT2:
private void populateObatacles(){
int currY = -5*Constants.SCREEN_HEIGHT/4;
while(currY < 0){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(new Obstacle(obstacleHeight, color, xStart, currY, playerGap));
currY += obstacleHeight + obstacleGap;
}
}
EDIT3:
if (obstacles.get(lastLineScored-1).getRectangle().top >= currY) {
score++;
lastLineScored--;
}
if (obstacles.get(obstacles.size()-1).getRectangle().top >= Constants.SCREEN_HEIGHT ){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(0, new Obstacle(obstacleHeight, color, xStart, obstacles.get(0).getRectangle().top - obstacleHeight - obstacleGap, playerGap));
obstacles.remove(obstacles.size()-1);
lastLineScored++;
}
}
}
public void draw(Canvas canvas){
for(Obstacle ob : obstacles)
ob.draw(canvas);
Paint paint = new Paint();
paint.setTextSize(100);
paint.setColor(Color.GREEN);
canvas.drawText("" + score , 50, 100 + paint.descent()- paint.descent (), paint);
EDIT4:
Obstacle manager http://pastebin.com/6E77QtHj
Obstacle http://pastebin.com/p33mPrat
Change your test. Instead of checking whether the top of the line is greater than SCREEN_HEIGHT, check whether it is greater than the character's Y coordinate. Also, you need to check against each
if (obstacles.get(obstacles.size()-1).getRectangle().top >= /** character Y coordinate */) {
score++;
}
if (obstacles.get(obstacles.size()-1).getRectangle().top >= Constants.SCREEN_HEIGHT ){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(0, new Obstacle(obstacleHeight, color, xStart, obstacles.get(0).getRectangle().top - obstacleHeight - obstacleGap, playerGap));
obstacles.remove(obstacles.size()-1);
}
EDIT: You need to keep track of which lines are already past the character and only up the score when a new line goes past. You'll need to keep an index of which lines have already been scored. So create a field to keep that index; let's call it lastScoredLine. It should be initialized to obstacles.size(). You'll have to adjust that field whenever a line falls off the screen.
if (obstacles.get(lastLineScored-1).getRectangle().top >= /** character Y coordinate */) {
score++;
lastLineScored--;
}
if (obstacles.get(obstacles.size()-1).getRectangle().top >= Constants.SCREEN_HEIGHT ){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(0, new Obstacle(obstacleHeight, color, xStart, obstacles.get(0).getRectangle().top - obstacleHeight - obstacleGap, playerGap));
obstacles.remove(obstacles.size()-1);
lastLineScored++;
}
I want to save bitmap without transparent area.
Bitmap has large transparent pixel.
So i want to remove that
How can i do this?
I cant add picture so explain with symbols.
I dont want to crop function.
I hope use filter
┌────────────────────────┐
│ transparent area
│ ┌────────┐
│ crop this
└────────┘
└────────────────────────┘
To find the non-transparent area of your bitmap, iterate across the bitmap in x and y and find the min and max of the non-transparent region. Then crop the bitmap to those co-ordinates.
Bitmap CropBitmapTransparency(Bitmap sourceBitmap)
{
int minX = sourceBitmap.getWidth();
int minY = sourceBitmap.getHeight();
int maxX = -1;
int maxY = -1;
for(int y = 0; y < sourceBitmap.getHeight(); y++)
{
for(int x = 0; x < sourceBitmap.getWidth(); x++)
{
int alpha = (sourceBitmap.getPixel(x, y) >> 24) & 255;
if(alpha > 0) // pixel is not 100% transparent
{
if(x < minX)
minX = x;
if(x > maxX)
maxX = x;
if(y < minY)
minY = y;
if(y > maxY)
maxY = y;
}
}
}
if((maxX < minX) || (maxY < minY))
return null; // Bitmap is entirely transparent
// crop bitmap to non-transparent area and return:
return Bitmap.createBitmap(sourceBitmap, minX, minY, (maxX - minX) + 1, (maxY - minY) + 1);
}
Crop transparent border with this github.
public static Bitmap crop(Bitmap bitmap) {
int height = bitmap.getHeight();
int width = bitmap.getWidth();
int[] empty = new int[width];
int[] buffer = new int[width];
Arrays.fill(empty, 0);
int top = 0;
int left = 0;
int bottom = height;
int right = width;
for (int y = 0; y < height; y++) {
bitmap.getPixels(buffer, 0, width, 0, y, width, 1);
if (!Arrays.equals(empty, buffer)) {
top = y;
break;
}
}
for (int y = height - 1; y > top; y--) {
bitmap.getPixels(buffer, 0, width, 0, y, width, 1);
if (!Arrays.equals(empty, buffer)) {
bottom = y;
break;
}
}
empty = new int[height];
buffer = new int[height];
Arrays.fill(empty, 0);
for (int x = 0; x < width; x++) {
bitmap.getPixels(buffer, 0, 1, x, 0, 1, height);
if (!Arrays.equals(empty, buffer)) {
left = x;
break;
}
}
for (int x = width - 1; x > left; x--) {
bitmap.getPixels(buffer, 0, 1, x, 0, 1, height);
if (!Arrays.equals(empty, buffer)) {
right = x;
break;
}
}
return Bitmap.createBitmap(bitmap, left, top, right - left + 1, bottom - top + 1);
}
I took #Alvaro Menezes's answer and improved it as a Kotlin extension function. I tweaked it a bit, changed some variable names for better readability and it adds more fixes to the issue mentioned by #Ahamadullah Saikat that throws an IllegalArgumentException
Note that reading pixels by line improve a lot the performances against reading this independently as the accepted answer suggest.
/**
* Trims a bitmap borders of a given color.
*
*/
fun Bitmap.trim(#ColorInt color: Int = Color.TRANSPARENT): Bitmap {
var top = height
var bottom = 0
var right = width
var left = 0
var colored = IntArray(width, { color })
var buffer = IntArray(width)
for (y in bottom until top) {
getPixels(buffer, 0, width, 0, y, width, 1)
if (!Arrays.equals(colored, buffer)) {
bottom = y
break
}
}
for (y in top - 1 downTo bottom) {
getPixels(buffer, 0, width, 0, y, width, 1)
if (!Arrays.equals(colored, buffer)) {
top = y
break
}
}
val heightRemaining = top - bottom
colored = IntArray(heightRemaining, { color })
buffer = IntArray(heightRemaining)
for (x in left until right) {
getPixels(buffer, 0, 1, x, bottom, 1, heightRemaining)
if (!Arrays.equals(colored, buffer)) {
left = x
break
}
}
for (x in right - 1 downTo left) {
getPixels(buffer, 0, 1, x, bottom, 1, heightRemaining)
if (!Arrays.equals(colored, buffer)) {
right = x
break
}
}
return Bitmap.createBitmap(this, left, bottom, right - left, top - bottom)
}
Following the official doc:
The new bitmap may be the same object as source, or a copy may have been made.
You should take into account when you execute .recycle() with the source bitmap.
I have a canvas!
1.On canvas i'm draw are graph (see below)!
2.Draw a points and lines between.
3.I'm was calculate are all points on a line, and draw little lines to bottom.
4.And draw gradient with are #e5fafd color!
private void drawGradient(Canvas canvas){
Paint gradientPainter = new Paint();
gradientPainter.setStrokeWidth(5);
gradientPainter.setShader(new LinearGradient(0, 0, 0, getHeight(), getResources().getColor(R.color.graph_gradient_start), Color.TRANSPARENT, Shader.TileMode.CLAMP));
for(int i = 0; i < ourCoords.size(); i++){
if(i != ourCoords.size() - 1){
final float x = ourCoords.get(i+1).getxCoord() - ourCoords.get(i).getxCoord();
final float y = ourCoords.get(i+1).getyCoord() - ourCoords.get(i).getyCoord();
float xT = ourCoords.get(i).getxCoord(), yT = ourCoords.get(i).getyCoord();
final float percentX = (float) (x / 100.0);
final float percentY = (float) (y / 100.0);
for(float j= (float) 0.1; j<100.0; j++){
float startX = (percentX * j) + xT;
float startY = (percentY * j) + yT;
float endX = startX;
float endY = startY + 400;
if(startX < (canvasWidth - marginLeft - 8)){
canvas.drawLine(startX, startY, endX, endY, gradientPainter);
}
}
}
drawPoints(canvas, ourCoords.get(i).getxCoord(), ourCoords.get(i).getyCoord(), 20);
}
}
I ended up getting a black gradient. Please help me and sorry for the mistakes.
See screen http://mepic.ru/view/?id=b1904b5d5b6d156e83206ab0c485d40a
This question is in continuation to my previous doubt
Now I am trying to make a guage view. I am drawing a scale but the alignment is not proper and I am not able to figure out the problem. Here is my code:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
w= canvas.getWidth();
h=canvas.getHeight();
DrawRange(canvas,innerRadius,outerRadius);
}
here innerRadius =250;
and outer radius = 300;
private void DrawRange(Canvas canvas,int r,int R) {
RectF rect = new RectF(canvas.getWidth()/2- r, canvas.getHeight()/2-r, canvas.getWidth()/2+r, canvas.getHeight()/2+r);
RectF rect1 = new RectF(canvas.getWidth()/2- R, canvas.getHeight()/2-R, canvas.getWidth()/2+R, canvas.getHeight()/2+R);
Paint scalePaint = new Paint();
scalePaint.setStyle(Paint.Style.STROKE);
scalePaint.setColor(0x9f004d0f);
scalePaint.setStrokeWidth(2);
scalePaint.setAntiAlias(true);
scalePaint.setTextSize(35.0f);
scalePaint.setTypeface(Typeface.SANS_SERIF);
scalePaint.setTextScaleX(0.4f);
scalePaint.setTextAlign(Paint.Align.CENTER);
canvas.drawOval(rect1, scalePaint);
canvas.drawOval(rect, scalePaint);
canvas.save(Canvas.CLIP_SAVE_FLAG);
int xc = 0;
for (int i = 0; i < totalNicks; i++) {
float y1 = 330;
float y2 = y1 + 5;
if (i % 5 == 0) {
canvas.drawText(""+xc, r-15, y2 , scalePaint);
xc+=5;
}else{
canvas.drawLine(r, y1, r, y2, scalePaint);
}
canvas.rotate(degreesPerNick, w/2, h/2);
}
canvas.restore();
}
I wonder if you are drawing the text and the dashes in the wrong place. The key reference point is the centre of the circles:
int cX = canvas.getWidth()/2;
int cY = canvas.getHeight()/2;
The other key reference is the difference between the two radii:
int deltaR = R - r;
The dashes and text are always drawn at 12 o'clock, say 20% above the inner circle to 1/3 of the way from the outer circle:
int dashInnerY = cY - r - deltaR/5; // 20% of the way between inner and outer radii
int dashOuterY = cY - R + deltaR/3; // 1/3 of the way between outer and inner radii
Then to render a dash:
canvas.drawLine(cX, dashInnerY, cX, dashOuterY, scalePaint);
And the number:
canvas.drawText(""+xc, cX, dashInnerY, scalePaint);