How to use android canvas to draw a Rectangle with only topleft and topright corners round?

How to use android canvas to draw a Rectangle with only topleft and topright corners round?

Salvatrix Ngải Author: Salvatrix Ngải Date: 2022-08-18
How to use android canvas to draw a Rectangle with only topleft and topright corners round?

All you need to know about How to use android canvas to draw a Rectangle with only topleft and topright corners round? , in addintion to android - How to get user location only once without tracking? , How to use Path (android.graphics.Path) with canvas in android? , canvas - Android - drawline with hardware acceleration and antialiasing causes artifacts , android - How canvas.drawRect draws a rectangle

  1. How to use android canvas to draw a Rectangle with only topleft and topright corners round?
  2. Question:

    I found a function for rectangles with all 4 corners being round, but I want to have just the top 2 corners round. What can I do?

    canvas.drawRoundRect(new RectF(0, 100, 100, 300), 6, 6, paint);
    


    Solution 1:

    Use a path. It has the advantage of working for APIs less than 21 (Arc is also limited thusly, which is why I quad). Which is a problem because not everybody has Lollipop yet. You can however specify a RectF and set the values with that and use arc back to API 1, but then you wouldn't get to use a static (without declaring a new object to build the object).

    Drawing a rounded rect:

        path.moveTo(right, top + ry);
        path.rQuadTo(0, -ry, -rx, -ry);
        path.rLineTo(-(width - (2 * rx)), 0);
        path.rQuadTo(-rx, 0, -rx, ry);
        path.rLineTo(0, (height - (2 * ry)));
        path.rQuadTo(0, ry, rx, ry);
        path.rLineTo((width - (2 * rx)), 0);
        path.rQuadTo(rx, 0, rx, -ry);
        path.rLineTo(0, -(height - (2 * ry)));
        path.close();
    

    As a full function:

    static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) {
        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);
    
        if (conformToOriginalPost) {
            path.rLineTo(0, ry);
            path.rLineTo(width, 0);
            path.rLineTo(0, -ry);
        }
        else {
            path.rQuadTo(0, ry, rx, ry);//bottom-left corner
            path.rLineTo(widthMinusCorners, 0);
            path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
        }
    
        path.rLineTo(0, -heightMinusCorners);
    
        path.close();//Given close, last lineto can be removed.
    
        return path;
    }
    

    You'd want to line all the way to those corner bits, rather than quad across them. This is what setting true to conformToOriginalPost does. Just line to the control point there.

    If you want to do that all but don't care about pre-Lollipop stuff, and urgently insist that if your rx and ry are high enough, it should draw a circle.

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) {
        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.arcTo(right - 2*rx, top, right, top + 2*ry, 0, -90, false); //top-right-corner
        path.rLineTo(-widthMinusCorners, 0);
        path.arcTo(left, top, left + 2*rx, top + 2*ry, 270, -90, false);//top-left corner.
        path.rLineTo(0, heightMinusCorners);
        if (conformToOriginalPost) {
            path.rLineTo(0, ry);
            path.rLineTo(width, 0);
            path.rLineTo(0, -ry);
        }
        else {
            path.arcTo(left, bottom - 2 * ry, left + 2 * rx, bottom, 180, -90, false); //bottom-left corner
            path.rLineTo(widthMinusCorners, 0);
            path.arcTo(right - 2 * rx, bottom - 2 * ry, right, bottom, 90, -90, false); //bottom-right corner
        }
    
        path.rLineTo(0, -heightMinusCorners);
    
        path.close();//Given close, last lineto can be removed.
        return path;
    }
    

    So, conformToOriginalPost actually draws a rounded rect without the bottom two bits rounded.

    arcquadimage

    Solution 2:

    For API 21 and above the Path class added a new method addRoundRect() which you can use it like this.

    corners = new float[]{
        80, 80,        // Top left radius in px
        80, 80,        // Top right radius in px
        0, 0,          // Bottom right radius in px
        0, 0           // Bottom left radius in px
    };
    
    final Path path = new Path();
    path.addRoundRect(rect, corners, Path.Direction.CW);
    canvas.drawPath(path, mPaint);
    

    in Kotlin

    val corners = floatArrayOf(
        80f, 80f,   // Top left radius in px
        80f, 80f,   // Top right radius in px
        0f, 0f,     // Bottom right radius in px
        0f, 0f      // Bottom left radius in px
    )
    
    val path = Path()
    path.addRoundRect(rect, corners, Path.Direction.CW)
    canvas.drawPath(path, mPaint)
    

    Solution 3:

    I would draw two rectangles:

    canvas.drawRect(new RectF(0, 110, 100, 290), paint);
    canvas.drawRoundRect(new RectF(0, 100, 100, 200), 6, 6, paint);
    

    Or something like that, you just overlap them so that the upper corners will be round. Preferably you should write a method for this

  3. android - How to get user location only once without tracking?
  4. Question:

    In my android app, I need to gt the user location when he clicks a button. I do not need to receive continuous updates on his location however.

    I searched through a few questions on stackoverflow, but the answers are 2-3 years old, so I was wondering, as on the Android SDK now, what is the best way to do it.

    Also, I would like not to get null in the location if possible.

    Thanks in advance.


    Solution 1:

    UPDATE September 23, 2020

    Change log of version 17.1.0 mentions a new way to get current location:

    FusedLocationProviderClient.getCurrentLocation()

    A single fresh location will be returned if the device location can be determined within reasonable time (tens of seconds), otherwise null will be returned. This method may return locations that are a few seconds old, but never returns much older locations. This is suitable for foreground applications that need a single fresh current location.

    Documentation: https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient#getCurrentLocation(int,%20com.google.android.gms.tasks.CancellationToken)

    Example of usage:

    val cancellationTokenSource = CancellationTokenSource()
    fusedLocationProviderClient.getCurrentLocation(LocationRequest.PRIORITY_HIGH_ACCURACY, cancellationTokenSource.token)
    
    // onStop or whenever you want to cancel the request
    cancellationTokenSource.cancel()
    

    Old Answer

    You can use setNumUpdates method and pass the value 1. example:

    mLocationRequest = new LocationRequest();
    mLocationRequest.setNumUpdates(1);
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    

    By default locations are continuously updated until the request is explicitly removed, however you can optionally request a set number of updates. For example, if your application only needs a single fresh location, then call this method with a value of 1 before passing the request to the location client.

    https://developers.google.com/android/reference/com/google/android/gms/location/LocationRequest.html#setNumUpdates(int)

    Solution 2:

    Android introduce Fused Location in last I/O Summit, Fused location provide you more reliable and accurate location with the best available provider.

    import android.location.Location;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import com.google.android.gms.common.ConnectionResult;
    import com.google.android.gms.common.api.GoogleApiClient;
    import com.google.android.gms.location.LocationListener;
    import com.google.android.gms.location.LocationRequest;
    import com.google.android.gms.location.LocationServices;
    
    public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener, LocationListener {
    
    
        TextView txtOutputLat, txtOutputLon;
        Location mLastLocation;
        private GoogleApiClient mGoogleApiClient;
        private LocationRequest mLocationRequest;
        String lat, lon;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            GoogleApiClient();
        }
    
    
        @Override
        public void onConnected(Bundle bundle) {
    
    
            mLocationRequest = LocationRequest.create();
            mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
            mLocationRequest.setInterval(100); // Update location every second
    
            //use if you want location update
            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,              mLocationRequest, this);
    
            // here you get current location
            mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
                                                                              mGoogleApiClient);
            if (mLastLocation != null) {
                lat = String.valueOf(mLastLocation.getLatitude());
                lon = String.valueOf(mLastLocation.getLongitude());
    
            }
    
        }
    
        @Override
        public void onConnectionSuspended(int i) {
    
        }
    
        @Override
        public void onLocationChanged(Location location) {
            lat = String.valueOf(location.getLatitude());
            lon = String.valueOf(location.getLongitude());
    
        }
    
        @Override
        public void onConnectionFailed(ConnectionResult connectionResult) {
            GoogleApiClient();
        }
    
        synchronized void GoogleApiClient() {
            mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(LocationServices.API)
            .build();
    
    
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            mGoogleApiClient.connect();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mGoogleApiClient.disconnect();
        }
    
    
    }
    

    Solution 3:

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>  
    
    
    
    if (mGoogleApiClient == null) {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(LocationServices.API)
            .build();
    }
    
    
           protected void onStart() {
        mGoogleApiClient.connect();
        super.onStart();
    }
    
    protected void onStop() {
        mGoogleApiClient.disconnect();
        super.onStop();
    }
    
    
    
     public class MainActivity extends ActionBarActivity implements
        ConnectionCallbacks, OnConnectionFailedListener {
    
    @Override
    public void onConnected(Bundle connectionHint) {
        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
                mGoogleApiClient);
        if (mLastLocation != null) {
            mLatitudeText.setText(String.valueOf(mLastLocation.getLatitude()));
            mLongitudeText.setText(String.valueOf(mLastLocation.getLongitude()));
        }
    }
    }
    

    for further reference see here

  5. How to use Path (android.graphics.Path) with canvas in android?
  6. Question:

    I am working on a Custom View that implements Catch application like Circular Menu. After spending a lot of time, I have made a bit of progress, completed outer semi-circle with multile colors. Now, reading an answer provided by developer of Catch application to an user for his query, I came across the class Path. Google Android Developer page does not provides enough material to be understood and to be familiar with Path. so, Please ? Anyone ?

    Thanks in advance.


    Solution 1:

    You can use it to draw lines on a canvas. A path is basically a collection of lines. You can use it to create shapes that are not standard. E.g. there a lots of functions to create some default shapes:

    canvas.drawRect();
    canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint);
    canvas.drawCircle(float cx, float cy, float radius, Paint paint);
    canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);
    canvas.drawOval(RectF oval, Paint paint);
    canvas.drawRect(float left, float top, float right, float bottom, Paint paint);
    

    But if you want something custom, you can create a path, and by calling

    // Set the beginning of the next contour to the point (x,y).
    void     moveTo(float x, float y)
    
    // Add a line from the last point to the specified point (x,y).
    void     lineTo(float x, float y)
    

    You have control of the pencil that draws the lines of your path. Here's a nice tutorial

  7. canvas - Android - drawline with hardware acceleration and antialiasing causes artifacts
  8. Question:

    I am working on an Android custom graph view that uses Canvas#drawLines and a paint object that has antialiasing turned on. My view has hardware acceleration turned on. Occasionally when I pinch zoom in/out, some of the lines in my graph will appear disjointed and they sort of taper off into a gradient. If I change to software layer or disable antialiasing, the issue goes away. Is this a bug with drawLines or does someone have an idea of what might be going on?

    The first image exhibits the issue, the second image was moved slightly and demonstrates how the graph looks most of the time, with fully joined lines.

    (image demonstrates issue) enter image description here

    (image showing how graph should look - still couple minor gaps) enter image description here


    Solution 1:

    I think this post by Romain Guy answers some of your question: http://android-developers.blogspot.com/2011/03/android-30-hardware-acceleration.html

    Essentially, anti-aliasing is not supported by drawLines when hardware acceleration is turned on. Also remember that hardware acceleration won't always be 'better' for your app. If what you are drawing can be accelerated, your app will benefit from it, but for certain operations it might be worse.

    I believe that explains why your lines appear disjointed when hardware accelerated. I'm not too sure it explains why it works when you turn anti-aliasing off, though. I'd imagine it would appear disjointed even with anti-aliasing off, but clearly that is not the case!

    Solution 2:

    Try forcing a refresh after the resize gestures.

    Have a look at my old Accelerometer Toy app. (Yeah, it REALLY need updating...) If you don't see the problem with that app then I can probably help.

  9. android - How canvas.drawRect draws a rectangle
  10. Question:

    I have to create a custom view where I have to draw a rectangle. I'm trying to use the canvas.drawRect method. I wanted to create rectangles somethig like this enter image description here

    the gray colored one is my custom view which is extending the View class. Inside the onDraw method I'm trying to draw the rectangle.

    But actually I'm confused with the parameters of the drawRect method.

    As per the documentation

    /**
     * Draw the specified Rect using the specified paint. The rectangle will
     * be filled or framed based on the Style in the paint.
     *
     * @param left   The left side of the rectangle to be drawn
     * @param top    The top side of the rectangle to be drawn
     * @param right  The right side of the rectangle to be drawn
     * @param bottom The bottom side of the rectangle to be drawn
     * @param paint  The paint used to draw the rect
     */
    

    What I assume is that left and top forms the x,y coordinates of the starting point, and right is the width and bottom is the height. But it doesn't seem to work that way.

    I tried something like this to draw one rectangle but it does not draw anything

           paint.setColor(Color.BLUE);
           canvas.drawRect(5, canvas.getHeight()/2, 30, 30, paint );
    

    Can anyone please tell how exactly a rectangle is drawn using these values?

    It would be very helpful if someone could show the code to draw at least the first rectangle.

    My requirement is like, the number of inner rectangles are dynamic, so if I pass some 4 to this View, it should create 4 equal width rectangles horizontally. something like enter image description here

    Thanks in advance!!


    Solution 1:

    But actually I'm confused with the parameters of the drawRect method.

    The drawRect method requires only two coordinates to draw a rectangle. The top left corner and the bottom right corner. So the 4 points form these 2 coordinates in your canvas. Hope it is clear from the below images

    enter image description here

    P1 and P2 are points formed by (left,top) and (right,bottom), So the drawn rectangle would be like this.

    enter image description here

    To draw rectangles dynamically like you have shown in you image , try something like this

    int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}; // given some fixed colors
    

    in you onDraw method

    @Override
    protected void onDraw(Canvas canvas) {
    int padding = 5;
    float rectangleWidth = (getMeasuredWidth() - padding * 2) / colors.length;
    for (int i = 0; i < colors.length; i++) {
        paint.setColor(colors[i]);
        canvas.drawRect(padding + (rectangleWidth * i),
                        getMeasuredHeight() / 2,
                        padding + rectangleWidth * (i + 1),
                        getMeasuredHeight() - padding, paint
        ); // 5 px is the padding given to the canvas
    }
    

    }

    Solution 2:

    drawRect(float left, float top, float right, float bottom, Paint paint)
    

    It seems in your case the problem is that if right is less than left or bottom is less than top then the rectangle is not drawn. But it seems it happens only in some devices , as @David Medenjak commented

    I also recommend you to use the View dimensions and not the Canvas dimensions, that is better to use getWidth() & getHeight() and not Canvas.getWidth() & Canvas.getHeight()

    Solution 3:

    Use this method to draw rectangle at X & Y coordinate with width and height params

    fun drawRectangle(left: Int, top: Int, right: Int, bottom: Int, canvas: Canvas, paint: Paint?) {
            var right = right
            var bottom = bottom
            right = left + right // width is the distance from left to right
            bottom = top + bottom // height is the distance from top to bottom
            canvas.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), paint!!)
        }
    

    Usage

    //drawing rectangle

            drawRectangle(posX, posY, 60, 40, canvas, paint)