Custom Tab Indicator(With Arrow down like Indicator) - android

Is there a wat to make a indicator like this?
it has some pointed arrow down like in the selected item?

The only solution I could find is to grab the source code of the original TabLayout and customize it, according your needs.
In fact, all you need to do to get this custom pointing arrow is to override SlidingTabStrip's void draw(Canvas canvas) method. Unfortunately, SlidingTabStrip is private inner class inside TabLayout.
Luckily, all support library code is open, so we can create our own TabLayoutWithArrow class. I replaced the standard void draw(Canvas canvas) by this one to draw the arrow:
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// i used <dimen name="pointing_arrow_size">3dp</dimen>
int arrowSize = getResources().getDimensionPixelSize(R.dimen.pointing_arrow_size);
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight - arrowSize,
mIndicatorRight, getHeight() - arrowSize, mSelectedIndicatorPaint);
canvas.drawPath(getTrianglePath(arrowSize), mSelectedIndicatorPaint);
}
}
private Path getTrianglePath(int arrowSize) {
mSelectedIndicatorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mSelectedIndicatorPaint.setAntiAlias(true);
int leftPointX = mIndicatorLeft + (mIndicatorRight - mIndicatorLeft) / 2 - arrowSize*2;
int rightPointX = leftPointX + arrowSize*2;
int bottomPointX = leftPointX + arrowSize;
int leftPointY = getHeight() - arrowSize;
int bottomPointY = getHeight();
Point left = new Point(leftPointX, leftPointY);
Point right = new Point(rightPointX, leftPointY);
Point bottom = new Point(bottomPointX, bottomPointY);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.setLastPoint(left.x, left.y);
path.lineTo(right.x, right.y);
path.lineTo(bottom.x, bottom.y);
path.lineTo(left.x, left.y);
path.close();
return path;
}
Of course, the background, the particular design of the indicator can be improved/adjust according your needs.
To make my custom TabLayoutWithArrow, I had to copy these files into my project:
AnimationUtils
TabLayout
ThemeUtils
ValueAnimatorCompat
ValueAnimatorCompatImplEclairMr1
ValueAnimatorCompatImplHoneycombMr1
ViewUtils
ViewUtilsLollipop
To have transparency behind the arrow, you just need to set this Shape-drawable, as a background for the TabLayoutWithArrow :
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:bottom="#dimen/pointing_arrow_size">
<shape android:shape="rectangle" >
<solid android:color="#FFFF00" />
</shape>
</item>
<item android:height="#dimen/pointing_arrow_size"
android:gravity="bottom">
<shape android:shape="rectangle" >
<solid android:color="#00000000" />
</shape>
</item>
</layer-list>
And the actual usage is:
<klogi.com.viewpagerwithdifferentmenu.CustomTabLayout.TabLayoutWithArrow
android:id="#+id/tabLayout"
android:background="#drawable/tab_layout_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
I've uploaded the whole project (the TabLayoutWithArrow + one-page app which is using it) to my dropbox - feel free to check it out.
I hope, it helps

now it doesn't work, tintmanager class is removed from support library 23.2.0 , I managed same functionality by changing background drawable at runtime inside for loop detecting clicked position , PS : check this question and answer I am using same library : https://github.com/astuetz/PagerSlidingTabStrip/issues/141

Here is the code for anyone requiring an upward triangle using #Konstantin Loginov code
private Path getTrianglePath(int arrowSize) {
mSelectedIndicatorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mSelectedIndicatorPaint.setAntiAlias(true);
mSelectedIndicatorPaint.setColor(Color.WHITE);
int leftPointX = mIndicatorLeft + (mIndicatorRight - mIndicatorLeft) / 2 - arrowSize * 1 / 2;
int mTopX = leftPointX + arrowSize;
int mTopY = getHeight() - arrowSize;
int rightPointX = leftPointX + arrowSize * 2;
int leftPointY = getHeight();
Point left = new Point(leftPointX, leftPointY);
Point right = new Point(rightPointX, leftPointY);
Point bottom = new Point(mTopX, mTopY);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.setLastPoint(left.x, left.y);
path.lineTo(right.x, right.y);
path.lineTo(bottom.x, bottom.y);
path.lineTo(left.x, left.y);
path.close();
return path;
}

Related

View Pager Indicator Android [duplicate]

Is there a wat to make a indicator like this?
it has some pointed arrow down like in the selected item?
The only solution I could find is to grab the source code of the original TabLayout and customize it, according your needs.
In fact, all you need to do to get this custom pointing arrow is to override SlidingTabStrip's void draw(Canvas canvas) method. Unfortunately, SlidingTabStrip is private inner class inside TabLayout.
Luckily, all support library code is open, so we can create our own TabLayoutWithArrow class. I replaced the standard void draw(Canvas canvas) by this one to draw the arrow:
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// i used <dimen name="pointing_arrow_size">3dp</dimen>
int arrowSize = getResources().getDimensionPixelSize(R.dimen.pointing_arrow_size);
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight - arrowSize,
mIndicatorRight, getHeight() - arrowSize, mSelectedIndicatorPaint);
canvas.drawPath(getTrianglePath(arrowSize), mSelectedIndicatorPaint);
}
}
private Path getTrianglePath(int arrowSize) {
mSelectedIndicatorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mSelectedIndicatorPaint.setAntiAlias(true);
int leftPointX = mIndicatorLeft + (mIndicatorRight - mIndicatorLeft) / 2 - arrowSize*2;
int rightPointX = leftPointX + arrowSize*2;
int bottomPointX = leftPointX + arrowSize;
int leftPointY = getHeight() - arrowSize;
int bottomPointY = getHeight();
Point left = new Point(leftPointX, leftPointY);
Point right = new Point(rightPointX, leftPointY);
Point bottom = new Point(bottomPointX, bottomPointY);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.setLastPoint(left.x, left.y);
path.lineTo(right.x, right.y);
path.lineTo(bottom.x, bottom.y);
path.lineTo(left.x, left.y);
path.close();
return path;
}
Of course, the background, the particular design of the indicator can be improved/adjust according your needs.
To make my custom TabLayoutWithArrow, I had to copy these files into my project:
AnimationUtils
TabLayout
ThemeUtils
ValueAnimatorCompat
ValueAnimatorCompatImplEclairMr1
ValueAnimatorCompatImplHoneycombMr1
ViewUtils
ViewUtilsLollipop
To have transparency behind the arrow, you just need to set this Shape-drawable, as a background for the TabLayoutWithArrow :
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:bottom="#dimen/pointing_arrow_size">
<shape android:shape="rectangle" >
<solid android:color="#FFFF00" />
</shape>
</item>
<item android:height="#dimen/pointing_arrow_size"
android:gravity="bottom">
<shape android:shape="rectangle" >
<solid android:color="#00000000" />
</shape>
</item>
</layer-list>
And the actual usage is:
<klogi.com.viewpagerwithdifferentmenu.CustomTabLayout.TabLayoutWithArrow
android:id="#+id/tabLayout"
android:background="#drawable/tab_layout_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
I've uploaded the whole project (the TabLayoutWithArrow + one-page app which is using it) to my dropbox - feel free to check it out.
I hope, it helps
now it doesn't work, tintmanager class is removed from support library 23.2.0 , I managed same functionality by changing background drawable at runtime inside for loop detecting clicked position , PS : check this question and answer I am using same library : https://github.com/astuetz/PagerSlidingTabStrip/issues/141
Here is the code for anyone requiring an upward triangle using #Konstantin Loginov code
private Path getTrianglePath(int arrowSize) {
mSelectedIndicatorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mSelectedIndicatorPaint.setAntiAlias(true);
mSelectedIndicatorPaint.setColor(Color.WHITE);
int leftPointX = mIndicatorLeft + (mIndicatorRight - mIndicatorLeft) / 2 - arrowSize * 1 / 2;
int mTopX = leftPointX + arrowSize;
int mTopY = getHeight() - arrowSize;
int rightPointX = leftPointX + arrowSize * 2;
int leftPointY = getHeight();
Point left = new Point(leftPointX, leftPointY);
Point right = new Point(rightPointX, leftPointY);
Point bottom = new Point(mTopX, mTopY);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.setLastPoint(left.x, left.y);
path.lineTo(right.x, right.y);
path.lineTo(bottom.x, bottom.y);
path.lineTo(left.x, left.y);
path.close();
return path;
}

In Android, How do I create a ring with dynamically changing colors and a transparent center / outside?

I am trying to copy this dynamic image:
The goal here is to alter the percentage of the circle with a certain color and the rest of a circle the other color depending on circumstances via java code in real-time. (IE, setting 50/50 would be half purple and half blue)
The tricky part is that the circle itself has solid colors but both outside and inside are transparent as there are items behind it that need to be seen; which is where I am getting stuck.
I would love some help figuring out how to try and make this work using either native Android properties or using a library if someone can recommend one.
What I have tried so far:
1) Making 2 circles with transparent outer and inner rings:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Outer layer of circle -->
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thicknessRatio="2"
android:useLevel="false">
<solid android:color="#android:color/transparent" />
<stroke
android:width="2dp"
android:color="#A9A9A9" />
</shape>
</item>
<!-- Inner layer of Circle -->
<item
android:left="12dp"
android:right="12dp"
android:top="12dp"
android:bottom="12dp">
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thicknessRatio="2"
android:useLevel="false">
<solid android:color="#android:color/transparent" />
<stroke
android:width="2dp"
android:color="#A9A9A9" />
</shape>
</item>
</layer-list>
And then included them in a layout with a split linear layout on each side like this:
But I am unsure how to make sections on the outside and inside of the circles invisible AND transparent while making the circle portion visible and NOT transparent.
2) mimicking this code to try and adapt to my own: https://github.com/DmitryMalkovich/circular-with-floating-action-button/blob/master/progress-fab/src/main/java/com/dmitrymalkovich/android/ProgressFloatingActionButton.java
3) Working with progress bars to try and set a percentage and then work the "not set" percentage part to the other color.
All three have not gotten me very far :(
Has anyone successfully done something like this and if so can they tell me the best way to go about recreating it? Thank you!
I ended up making a custom class that was derived from this answer: Android: looking for a drawArc() method with inner & outer radius
Code is below:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Silmarilos on 2017-05-22.
*/
public class MultiColorCircle extends View {
private RectF rect, outerRect, innerRect;
private Paint perimeterPaint;
private List<CustomStrokeObject> strokeObjects;
private int widthOfCircleStroke, widthOfBoarderStroke,
colorOfBoarderStroke, onePercentPixels;
public MultiColorCircle(Context context) {
super(context);
init();
}
public MultiColorCircle(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public MultiColorCircle(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MultiColorCircle(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
/**
* Setter for the width of the circle stroke. Affects all arcs drawn. This is the width of
* the various arcs that make up the actual circle, this is NOT the boarder, that is different
* #param widthOfCircleStroke
*/
public void setWidthOfCircleStroke(int widthOfCircleStroke){
this.widthOfCircleStroke = widthOfCircleStroke;
}
/**
* Setter for the width of the boarder stroke. This is the width of the boarder strokes used
* to make the inner and outer boarder of the rings that surround the main body circle.
* They will default to black and 1 pixel in width. To hide them, pass null as the color
* #param widthOfBoarderStroke
*/
public void setWidthOfBoarderStroke(int widthOfBoarderStroke){
this.widthOfBoarderStroke = widthOfBoarderStroke;
this.perimeterPaint.setStrokeWidth(this.widthOfBoarderStroke);
}
/**
* Set the color of the boarder stroke. Send in null if you want it to be hidden
* #param colorOfBoarderStroke
*/
public void setColorOfBoarderStroke(Integer colorOfBoarderStroke){
if(colorOfBoarderStroke == null){
//Set to transparent
this.colorOfBoarderStroke = Color.parseColor("#00000000");
} else {
this.colorOfBoarderStroke = colorOfBoarderStroke;
}
this.perimeterPaint.setColor(this.colorOfBoarderStroke);
}
private void init(){
this.strokeObjects = new ArrayList<>();
this.onePercentPixels = 0;
this.widthOfCircleStroke = 1; //Default
this.widthOfBoarderStroke = 1; //Default
this.colorOfBoarderStroke = Color.parseColor("#000000"); //Default, black
this.rect = new RectF();
this.outerRect = new RectF();
this.innerRect = new RectF();
this.perimeterPaint = new Paint();
this.perimeterPaint.setStrokeWidth(widthOfBoarderStroke);
this.perimeterPaint.setColor(colorOfBoarderStroke);
this.perimeterPaint.setAntiAlias(true);
this.perimeterPaint.setStyle(Paint.Style.STROKE);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = this.getWidth();
int left = 0;
int top = 0;
int right = (left + width);
int bottom = (top + width);
onePercentPixels = (int)(this.getWidth() * 0.01);
left = left + onePercentPixels + widthOfCircleStroke;
top = top + onePercentPixels + widthOfCircleStroke;
right = right - onePercentPixels - widthOfCircleStroke;
bottom = bottom - onePercentPixels - widthOfCircleStroke;
drawCircle(canvas, left, top, right, bottom);
}
private void drawCircle(Canvas canvas, int left, int top, int right, int bottom){
//Base rect for sides of circle parameters
rect.set(left, top, right, bottom);
if(this.strokeObjects.size() <= 0){
return;
}
for(CustomStrokeObject strokeObject : this.strokeObjects){
if(strokeObject == null){
continue;
}
Paint paint = strokeObject.paint;
paint.setStrokeWidth(this.widthOfCircleStroke);
canvas.drawArc(rect, strokeObject.percentToStartAt,
strokeObject.percentOfCircle, false, paint);
}
drawPerimeterCircle(canvas, left, top, right, bottom);
}
/**
* Draws the outer and inner boarder arcs of black to create a boarder
*/
private void drawPerimeterCircle(Canvas canvas, int left, int top, int right, int bottom){
//Base inner and outer rectanges for circles to be drawn
outerRect.set(
(left - (widthOfCircleStroke / 2)),
(top - (widthOfCircleStroke / 2)),
(right + (widthOfCircleStroke / 2)),
(bottom + (widthOfCircleStroke / 2))
);
innerRect.set(
(left + (widthOfCircleStroke / 2)),
(top + (widthOfCircleStroke / 2)),
(right - (widthOfCircleStroke / 2)),
(bottom - (widthOfCircleStroke / 2))
);
canvas.drawArc(outerRect, 0, 360, false, perimeterPaint);
canvas.drawArc(innerRect, 0, 360, false, perimeterPaint);
}
/**
* Setter method for setting the various strokes on the circle
* #param strokeObjects {#link CustomStrokeObject}
*/
public void setCircleStrokes(List<CustomStrokeObject> strokeObjects){
if(strokeObjects == null){
return;
}
if(strokeObjects.size() == 0){
return;
}
this.strokeObjects = new ArrayList<>();
this.strokeObjects = strokeObjects;
invalidate();
}
/**
* Class used in drawing arcs of circle
*/
public static class CustomStrokeObject {
float percentOfCircle;
float percentToStartAt;
Integer colorOfLine;
Paint paint;
/**
* Constructor. This will also do the calculations to convert the percentages into the
* circle numbers so that passing in 50 will be converted into 180 for mapping on to a
* circle. Also, I am adding in a very tiny amount of overlap (a couple pixels) so that
* there will not be a gap between the arcs because the whitespace gap of a couple pixels
* does not look very good. To remove this, just remove the -.1 and .1 to startAt and circle
* #param percentOfCircle Percent of the circle to fill.
* NOTE! THIS IS BASED OFF OF 100%!
* This is not based off of a full 360 circle so if you want something
* to fill half the circle, pass 50, not 180.
* #param percentToStartAt Percent to start at (for filling multiple colors).
* NOTE! THIS IS BASED OFF OF 100%!
* This is not based off of a full 360 circle so if you want something
* to fill half the circle, pass 50, not 180.
* #param colorOfLine Int color of the line to use
*/
public CustomStrokeObject(float percentOfCircle, float percentToStartAt, Integer colorOfLine){
this.percentOfCircle = percentOfCircle;
this.percentToStartAt = percentToStartAt;
this.colorOfLine = colorOfLine;
if(this.percentOfCircle < 0 || this.percentOfCircle > 100){
this.percentOfCircle = 100; //Default to 100%
}
this.percentOfCircle = (float)((360 * (percentOfCircle + 0.1)) / 100);
if(this.percentToStartAt < 0 || this.percentToStartAt > 100){
this.percentToStartAt = 0;
}
//-90 so it will start at top, Ex: http://www.cumulations.com/images/blog/screen1.png
this.percentToStartAt = (float)((360 * (percentToStartAt - 0.1)) / 100) - 90;
if(this.colorOfLine == null){
this.colorOfLine = Color.parseColor("#000000"); //Default to black
}
paint = new Paint();
paint.setColor(colorOfLine);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
}
/**
* Overloaded setter, in case you want to set a custom paint object here
* #param paint Paint object to overwrite one set by constructor
*/
public void setPaint(Paint paint){
this.paint = paint;
}
}
}
To use it, define it in the xml:
<com.yourpackage.goeshere.MultiColorCircle
android:id="#+id/my_circle"
android:padding="8dp"
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Then in your Java:
MultiColorCircle my_circle = (MultiColorCircle) this.findViewById(R.id.my_circle);
my_circle.setWidthOfCircleStroke(60);
my_circle.setWidthOfBoarderStroke(2);
my_circle.setColorOfBoarderStroke(ContextCompat.getColor(this, R.color.purple));
MultiColorCircle.CustomStrokeObject s1 = new MultiColorCircle.CustomStrokeObject(
50, 0, ContextCompat.getColor(this, R.color.blue)
);
MultiColorCircle.CustomStrokeObject s2 = new MultiColorCircle.CustomStrokeObject(
30, 50, ContextCompat.getColor(this, R.color.red)
);
MultiColorCircle.CustomStrokeObject s3 = new MultiColorCircle.CustomStrokeObject(
20, 80, ContextCompat.getColor(this, R.color.green)
);
List<MultiColorCircle.CustomStrokeObject> myList = new ArrayList<>();
myList.add(s1);
myList.add(s2);
myList.add(s3);
my_circle.setCircleStrokes(myList);
Adjust values accordingly.
Sil

View with round corners not smooth

Have a look at my code below.
ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape());
shapeDrawable.getPaint().setColor(Color.parseColor("#5a2705"));
shapeDrawable.getPaint().setStyle(Style.STROKE);
shapeDrawable.getPaint().setAntiAlias(true);
shapeDrawable.getPaint().setStrokeWidth(2);
shapeDrawable.getPaint().setPathEffect(new CornerPathEffect(10));
I am applying this as background to my LinearLayout, but the edges are not smooth. How can I fix this?
Here is the screenshot of how it looks.
Using a programmatically-created shape drawable as a View background results in the outer half of your stroke width getting cropped off (for reasons I don't know). Look closely at your image and you'll see that your stroke is only 1 pixel wide, even though you requested 2. That is why the corners look ugly. This effect will be much more apparent if you try a bigger stroke and radius such as 10 and 40, respectively.
Either use an XML drawable, which doesn't seem to have this problem, like in Harshit Jain's answer, or do the following if you must (or prefer to) use a programmatic solution.
Solution: Use a layer list to inset the rectangle by the amount that is being clipped (half the stroke width), like this:
float strokeWidth = 2;
ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape());
shapeDrawable.getPaint().setColor(Color.parseColor("#5a2705"));
shapeDrawable.getPaint().setStyle(Style.STROKE);
shapeDrawable.getPaint().setAntiAlias(true);
shapeDrawable.getPaint().setStrokeWidth(strokeWidth);
shapeDrawable.getPaint().setPathEffect(new CornerPathEffect(10));
Drawable[] layers = {shapeDrawable};
LayerDrawable layerDrawable = new LayerDrawable(layers);
int halfStrokeWidth = (int)(strokeWidth/2);
layerDrawable.setLayerInset(0, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth);
Then use the layerDrawable as your background. Here is a screen shot of the result of the above code:
You can try creating a separate xml file with a layout of the rounded rectangle. Such as:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="color_here"/>
<stroke android:width="5dp" android:color="color_here"/>
<corners android:radius="2dp"/>
</shape>
You can tune this to your liking and use this XML file as a background in your main XML.
You can also try using 9Patch which should already come with your SDK
To cater such background requirement I prefer using 9-patch drawable.
Below are the pointer to get going with creating and using 9-patch drawable resources:
Developer Doc: http://developer.android.com/tools/help/draw9patch.html
Another explanation: http://radleymarx.com/blog/simple-guide-to-9-patch/
Online tool to create 9-patch images. (Note: Modify the file extension to *.9.png while saving the image)
You might want to consider checking this out.
Rounded Image Views by Vince
I have solved this issue in a very simple way,
I was looking for a code which can be done programmatically and not via xml,
Here is the code:
GradientDrawable shape = new GradientDrawable();
shape.setCornerRadius(10);
shape.setColor(Color.WHITE);
shape.setStroke(2, Color.parseColor("#996633"));
You can set this like this : view.setBackgroundDrawable(shape);
The answer posted by #Tenfour04 might also solve the issue but i havent tried as such.
Hope it helps someone.
You may try using selector like this . This solution is the best you just need to set this selector to the background of the layout.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#color/white" />
<stroke android:width="1dp" android:color="#color/grey" />
<padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />
<corners android:radius="15dp" />
<gradient
android:angle="90"
android:startColor="#color/white"
android:centerColor="#color/grey"
android:endColor="#color/white"
android:type="linear" />
</shape>
Choose color of your own.
Cheers!
I've encountered this problem recently and came up with a different solution. IMHO the best solution is to create your own Shape implementation and use it to create a ShapeDrawable.
Below is a simple implementation of rounded rectangle, that will allow you to inset it's border.
class InsetRoundRectShape(
private var radiusArray: FloatArray = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f),
private var inset: RectF = RectF()
): RectShape() {
private var innerRect: RectF = RectF()
private var path: Path = Path()
constructor(radius: Float, inset: RectF): this(floatArrayOf(radius, radius, radius, radius, radius, radius, radius, radius), inset)
constructor(radius: Float, inset: Float): this(radius, RectF(inset, inset, inset, inset))
init {
if (radiusArray.size < 8) {
throw ArrayIndexOutOfBoundsException("radius array must have >= 8 values")
}
}
override fun draw(canvas: Canvas, paint: Paint) {
canvas.drawPath(path, paint)
}
override fun getOutline(outline: Outline) {
super.getOutline(outline)
val radius = radiusArray[0]
if(radiusArray.any { it != radius }) {
outline.setConvexPath(path)
return
}
val r = rect()
outline.setRoundRect(ceil(r.left).toInt(), ceil(r.top).toInt(), floor(r.right).toInt(), floor(r.bottom).toInt(), radius)
}
override fun onResize(w: Float, h: Float) {
super.onResize(w, h)
val r = rect()
path.reset()
innerRect.set(r.left + inset.left, r.top + inset.top, r.right - inset.right, r.bottom - inset.bottom)
if(innerRect.width() <= w && innerRect.height() <= h) {
path.addRoundRect(innerRect, radiusArray, Path.Direction.CCW)
}
}
override fun clone(): InsetRoundRectShape {
val shape = super.clone() as InsetRoundRectShape
shape.radiusArray = radiusArray.clone()
shape.inset = RectF(inset)
shape.path = Path(path)
return shape
}
}
Create ShapeDrawable like this
//Inset has to be half of strokeWidth
ShapeDrawable(InsetRoundRectShape(10f, 4f)).apply {
this.paint.color = Color.BLUE
this.paint.style = Paint.Style.STROKE
this.paint.strokeWidth = 8.dp
this.invalidateSelf()
}

Moving "Cloud" - Radial Gradient moving with finger

First Q. I have working code to make this move elsewhere in the file -- that's not the question. The question is how do I create a Radial Gradient that can be moved (below API 16).
Preempting snark, I've spent a lot of time here:
http://developer.android.com/reference/android/graphics/drawable/GradientDrawable.html
With GradientDrawable (below), there doesn't seem to be a way to set the colors without also setting a non-radial orientation.
public class CustomView extends View {
int width = (sWidth/8); // sWidth defined elsewhere as width of screen
int height = (sWidth/8);
GradientDrawable gradient;
int[] colors = {0x60ffffff,0x000000};
public CustomView(Context context) {
super(context);
gradient = new GradientDrawable(GradientDrawable.Orientation.BL_TR,colors);
}
protected void onDraw(Canvas canvas) {
if(x != 0 && y != 0){ // OnTouch calls invalidate on this view for movement
gradient.mutate();
gradient.setShape(GradientDrawable.RADIAL_GRADIENT);
// This just makes it disappear:
// setGradientType (GradientDrawable.RADIAL_GRADIENT);
gradient.setBounds(x-width/2, y-height/2, x + width, y + height);
gradient.draw(canvas);
}
}
}
There is also this:
http://developer.android.com/reference/android/graphics/RadialGradient.html
But there seems to be no way to move that gradient. Can you maybe put the radial gradient on a transparent circle of some kind that can then be moved? I'm at a loss. My thanks in advance.
Edit:
Step 1, define an oval shape in your drawable folder. This one is "cloud.xml":
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<gradient
android:centerX="0.5"
android:centerY="0.5"
android:endColor="#00000000"
android:gradientRadius="30"
android:startColor="#f0ffffff"
android:type="radial" />
<size
android:width="60dp"
android:height="60dp" />
</shape>
The radius, width and height will likely need to be changed dynamically. So put whatever. The color scheme above will give a slightly transparent color to fully transparent. Cloud effect.
Step 2, the constructor of your custom view:
// actually before the constructor this, of course:
GradientDrawable circle;
// now the constructor:
circle = (GradientDrawable) context.getResources().getDrawable(R.drawable.cloud);
Step 3, the onDraw method:
// x & y being coordinates updated from onTouch method,
// circleRad being some constant dependent on screen dp
if(x != 0 && y != 0){
circle.setGradientRadius(circleRad);
circle.setBounds(x-circleRad, y-circleRad,
x+circleRad, y+circleRad);
circle.draw(canvas);
}
------------- Original, Less Process Efficient Solution Preserved Below -----------
Wait a couple weeks and you can answer your own questions. Turns out it was RadialGradient the whole time.
public class CustomView extends View implements OnTouchListener {
Shader radialGradientShader;
Paint paint;
private int circleDiam;
private int x = 0;
private int y = 0;
private int lastScreenColor;
public CustomView(Context context, int circleDiam) {
super(context);
this.circleDiam = circleDiam;
paint = new Paint();
}
protected void onDraw(Canvas canvas) {
if(x != 0 && y != 0){
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
radialGradientShader = new
RadialGradient(x, y, circleDiam,
0xf0ffffff,0x00000000,Shader.TileMode.MIRROR);
paint.setShader(radialGradientShader);
canvas.drawCircle(x, y, circleDiam, paint);
}
}
public boolean onTouch(View v, MotionEvent event) {
x = (int)event.getX();
y = (int)event.getY();
if(event.getAction() == MotionEvent.ACTION_DOWN
&& event.getAction() == MotionEvent.ACTION_MOVE){
invalidate();
return true;
}
else{
x = 0;
y = 0;
invalidate();
return false;
}
}
}
A fluffy cloud!
The only problem with this solution is that Eclipse gets mad when you instantiate an object in the onDraw method. However, if you try to instantiate it in the constructor things get ugly fast.
Extra points for a solution that avoids said problem.

tilemode only in X

I have a xml file :
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/header"
android:tileMode="repeat" />
I have a header and I would like to repeat my drawable only in X and stretch it in Y. How could I do that in xml.
first try :
I remove my xml and I've done that at the beginning of my activity:
BitmapDrawable bp =(BitmapDrawable)getResources().getDrawable(R.drawable.header);
bp.setTileModeX(TileMode.REPEAT);
LinearLayout layout = (LinearLayout)findViewById(R.id.headerRedLayout);
layout.setBackgroundDrawable(bp);
It works great but I would like that the repeat starts at the bottom of the layout
In the onDraw() method do this
keep in mind R.drawable.bg_tile is your image
protected void onDraw (Canvas canvas) {
bgTile = BitmapFactory.decodeResource(context.getResources(), R.drawable.bg_tile);
float left = 0;
float top = 0;
float bgTileWidth = bgTile.getWidth();
int screenWidth = canvas.getWidth(); //Not sure of this
int screenHeight = canvas.getHeight(); //Not sure of this too
while (left < screenWidth) {
canvas.drawBitmap(
bgTile,
null,
new Rect(left,top,left+bgTileWidth,screenHeight),
null
);
left += bgTileWidth;
}
}

Categories

Resources