Like many other posts listed here i struggle with radial gradient.
post 1, 2, 3...
I want 2 things:
set a gradient around a button that has a selector as background drawable to get an aura effect
adjust this gradient radius to have the same feel independently of the screen resolution/size.
What I have done so far: Solution 1
I tried to implement several solutions found around. I show here my 2 best tries. I set a drawable with a gradient to the background of an enclosing layout around my button. That fulfills the point 1)
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/custom_background"
android:gravity="center"
android:padding="#dimen/padding_extra_large" >
<Button
android:id="#+id/snap_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/custom_button" />
</FrameLayout>
This is my #drawable/custom_background
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<!-- Circular gradient -->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:endColor="#ffdbdbdb"
android:gradientRadius="180"
android:startColor="#ff000000"
android:type="radial"
android:innerRadiusRatio="2"
/>
</shape>
</item>
What I have done so far: Solution 2
The effect is good but android:gradientRadius="180" has to be adjust for each screen. Post 1 provided some solution and I tried to customize a layout and a view but I got crashes. Some configuration did not fail (with background set to root layout but in this case onDraw was never called).
Customized view :
public class GradientView extends View {
Paint paint;
RadialGradient gradient;
int maxSize;
public GradientView(Context context) {
super(context);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
this.setWillNotDraw(false);
// TODO Auto-generated constructor stub
}
#Override
protected void onDraw(Canvas canvas) {
Dbg.e("RelativeLayoutSnapBook", " - onDraw !!!");
maxSize = Math.max(getHeight(), getWidth());
RadialGradient gradient = new RadialGradient(
getWidth()/2,
getHeight()/2,
maxSize,
new int[] {Color.RED, Color.BLACK},
new float[] {0, 1},
android.graphics.Shader.TileMode.CLAMP);
paint.setDither(true);
paint.setShader(gradient);
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
}
}
and this view goes here :
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="#dimen/padding_extra_large" >
<com.snapcar.rider.widget.GradientView
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="#+id/snap_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/snap_button" />
</FrameLayout>
These is the fail : error when inflating the fragment with stack and stack of intern error :
10-29 18:55:25.794: E/AndroidRuntime(11828): FATAL EXCEPTION: main
10-29 18:55:25.794: E/AndroidRuntime(11828): android.view.InflateException: Binary XML file line #33: Error inflating class com.xxx.widget.GradientView
10-29 18:55:25.794: E/AndroidRuntime(11828): Caused by: java.lang.ClassNotFoundException: com.xxx.rider.widget.GradientView in loader dalvik.system.PathClassLoader[/system/framework/com.google.android.maps.jar:/data/app/com.snapcar.rider-1.apk]
Can you help me to do that please ? Thanks a lot!
Edit : someone?
I solved it ! So much trouble :/ If you know Photoshop, you better go with it I think!
Thanks to this post that gave me the way to go : multiple-gradrient Only the first code extract was used but the whole post might interested you if you reached this post.
So I entirely define my gradient with code using a callback on global layout:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.v(TAG, "- onCreateView Fragment");
root = inflater.inflate(R.layout.fragment_layout, null);
root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Log.e(TAG, "- On Layout Completed ! ");
final FrameLayout frame = (FrameLayout) root.findViewById(R.id.frame_layout_gradient);
ShapeDrawable.ShaderFactory sf = new ShapeDrawable.ShaderFactory() {
#Override
public Shader resize(int width, int height) {
Log.e(TAG, " - onGlobalLayout : getWidth = "+ frame.getWidth() +" - getHeight = "+ frame.getHeight() );
RadialGradient lg = new RadialGradient(frame.getWidth()/2, frame.getHeight()/2,
frame.getWidth()/2, Color.BLACK, Color.parseColor("#FFdbdbdb"), TileMode.CLAMP);
return lg;
}
};
PaintDrawable p = new PaintDrawable();
p.setShape(new OvalShape());
p.setShaderFactory(sf);
frame.setBackgroundDrawable((Drawable)p);
}
}
);
This trick Color.parseColor("#FFdbdbdb") is need because the retard constructor of RadialGradient cannot read correctly the reference to your R.color.the_gray_I_want
The OvalShape does a circle in my case because my frameLayout is square.
With this solution, you have a shadowing totally independent of screen characteristics.
![the result - unvarying from SG3 to HTC wildfire!1
Related
I want to give the map a nice looking rounded corners as the two boxes below it have.
I can't do it with the map fragment it self because there is not a background property to a
fragment.
setting the map inside a layout and setting it background to a rounded shape didn't help me
as well and this is the result:
I could merge the map but this would make it smaller and i would like to avoid it.
EDIT:
#Ryan this is the new result #2:
I guess this is not bad, no even close to the corners on the other boxes,
but still not bad with a little more work a could get somewhere close i just dont have a normal image editor.
but one thing that still bothers me now is the separation between the "Location" Textview and the map it's self. could i painted the patch in other way so that there was now distance? this is how i did it:
Well I have finally figured this out:
this is what i used for the patch:
Thanks.
I know it's an old post, but you can try using Cards like so:
<android.support.v7.widget.CardView
android:layout_width="300dp"
android:layout_height="350dp"
android:layout_gravity="center_horizontal|center_vertical"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="20dp"
app:cardCornerRadius="12dp"
app:cardElevation="12dp">
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v7.widget.CardView>
I haven't tried this, but I'd put a view with rounded corners and a transparent middle on top of the mapView / mapFragment.
That is, put the mapFragment and the rounded corner view in a FrameLayout with both filling the FrameLayout, then make the middle of the rounded corner view transparent.
For further clarification, you could do it in a layout as follows:-
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="#+id/mapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/rounded_background"
android:orientation="vertical" >
</LinearLayout>
</FrameLayout>
The rounded_background is a 9-patch with rounded corners and a transparent middle. E.g.
Hope that helps,
Ryan
The easiest way is to wrap the map fragment inside a FrameLayout along with an ImageView. The Imageview would display a rounded rectangle on top of the map fragment. In its simplest form you will see the map fragment inside the rounded rectangle with its corners sticking out of the rounded rectangle because the map view itself is not rounded. To overcome this visual oddity simply apply a layout_margin value on map fragment. The value should be equal to the rectangle's border width.
<FrameLayout
android:id="#+id/map_container"
android:layout_width="wrap_content"
android:layout_height="340dp" >
<fragment
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="3dp"
android:name="com.google.android.gms.maps.SupportMapFragment"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/map_bg_box" />
</FrameLayout>
The rectangle drawable is defined as an xml shape as below
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="3dp"
android:color="#ff000000" />
<corners android:bottomRightRadius="7dp" android:bottomLeftRadius="7dp"
android:topLeftRadius="7dp" android:topRightRadius="7dp"/>
</shape>
Notice the stroke width of the rectangle is 3dp that is exactly the same value we applied to the layout_margin property of the map fragment. The result is a nicely round cornered map fragment as shown in the screenshot below
Wrap the map fragment in this layout:
package com.example.yourpackage;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
/**
* Just extend any Layout you like/need
*/
public class RoundedLayout extends RelativeLayout {
private Path mPathCorners = new Path();
private Path mPathCircle = new Path();
private float mCornerRadius;
/**
* border path
*/
private Path mPathCornersBorder = new Path();
private Path mPathCircleBorder = new Path();
private int mBorderWidth = 0;
private int mBorderHalf;
private boolean mShowBorder = false;
private int mBorderColor = 0xFFFF7700;
private float mDensity = 1.0f;
/**
* Rounded corners or circle shape
*/
private boolean mIsCircleShape = false;
private Paint mPaint = new Paint();
private float dpFromPx(final float px) {
return px / mDensity;
}
private float pxFromDp(final float dp) {
return dp * mDensity;
}
public RoundedLayout(Context context) {
this(context, null);
}
public RoundedLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundedLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDensity = getResources().getDisplayMetrics().density;
// just a default for corner radius
mCornerRadius = pxFromDp(25f);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mBorderColor);
setBorderWidth(Math.round(pxFromDp(2f)));
}
/**
* Switch to circle or rectangle shape
*
* #param useCircle
*/
public void setShapeCircle(boolean useCircle) {
mIsCircleShape = useCircle;
invalidate();
}
/**
* change corner radius
*
* #param radius
*/
public void setCornerRadius(int radius) {
mCornerRadius = radius;
invalidate();
}
public void showBorder(boolean show) {
mShowBorder = show;
invalidate();
}
public void setBorderWidth(int width) {
mBorderWidth = width;
mBorderHalf = Math.round(mBorderWidth / 2);
if (mBorderHalf == 0) {
mBorderHalf = 1;
}
mPaint.setStrokeWidth(mBorderWidth);
updateCircleBorder();
updateRectangleBorder();
invalidate();
}
public void setBorderColor(int color) {
mBorderColor = color;
mPaint.setColor(color);
invalidate();
}
// helper reusable vars, just IGNORE
private float halfWidth, halfHeight, centerX, centerY;
private RectF rect = new RectF(0, 0, 0, 0);
private RectF rectBorder = new RectF(0, 0, 0, 0);
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// just calculate both shapes, is not heavy
// rounded corners path
rect.left = 0;
rect.top = 0;
rect.right = w;
rect.bottom = h;
mPathCorners.reset();
mPathCorners.addRoundRect(rect, mCornerRadius, mCornerRadius, Path.Direction.CW);
mPathCorners.close();
// circle path
halfWidth = w / 2f;
halfHeight = h / 2f;
centerX = halfWidth;
centerY = halfHeight;
mPathCircle.reset();
mPathCircle.addCircle(centerX, centerY, Math.min(halfWidth, halfHeight), Path.Direction.CW);
mPathCircle.close();
updateRectangleBorder();
updateCircleBorder();
}
// helper reusable var, just IGNORE
private int save;
#Override
protected void dispatchDraw(Canvas canvas) {
save = canvas.save();
canvas.clipPath(mIsCircleShape ? mPathCircle : mPathCorners);
super.dispatchDraw(canvas);
canvas.restoreToCount(save);
if (mShowBorder) {
canvas.drawPath(mIsCircleShape ? mPathCircleBorder : mPathCornersBorder, mPaint);
}
}
private void updateCircleBorder() {
// border path for circle
mPathCircleBorder.reset();
mPathCircleBorder.addCircle(centerX, centerY, Math.min(halfWidth - mBorderHalf,
halfHeight - mBorderHalf), Path.Direction.CW);
mPathCircleBorder.close();
}
private void updateRectangleBorder() {
// border path for rectangle
rectBorder.left = rect.left + mBorderHalf;
rectBorder.top = rect.top + mBorderHalf;
rectBorder.right = rect.right - mBorderHalf;
rectBorder.bottom = rect.bottom - mBorderHalf;
mPathCornersBorder.reset();
mPathCornersBorder.addRoundRect(rectBorder, mCornerRadius - mBorderHalf, mCornerRadius -
mBorderHalf, Path.Direction.CW);
mPathCornersBorder.close();
}
}
In layout will be like this:
<com.example.yourpackage.RoundedLayout
android:id="#+id/maplayout"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="20dp">
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="200dp"
tools:context="com.example.yourpackage.MapsMarkerActivity"/>
</com.example.yourpackage.RoundedLayout>
In code can be like this for a round shape with border:
RoundedLayout rl = (RoundedLayout) findViewById(R.id.maplayout);
rl.setShapeCircle(true);
rl.showBorder(true);
rl.setBorderWidth(2);
This layout can be used to shape any view.
It's incredible how google is incapable of making competent (usable) complete demos for it's android API.
For other people looking into this, I just tackled this using GoogleMap.snapshot and manipulating the bitmap result with this stack over flow answer:
How to make an ImageView with rounded corners?
Mind you this is only valid if you are going to have a static map that is not going to be interacted with.
Make sure you take the snap shot after the map is loaded.
I updated the image view helper code to draw with path to support rounding only some corners. ie. If you want to round only 2 of the corners.
You just need the path round rect function that takes a float[]
I show a progress bar until I get a callback from GoogleMap loaded listener than I take the snapshot.
If you take your snapshot too early you will get can't create bitmap with 0 width and height error.
Hope this helps someone looking for rounded corners or other weird shape in static map snapshot.
If you are only trying to target API 21 (Lollipop) and higher
This is the easiest way possible.
parentView.setClipToOutline(true);
Result
Following #Nouman_Hanif post I ended up with a solution that looks quite good.
map_rounded_corner_overlay.xml:
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="1dp"
android:color="#color/white" />
<corners android:radius="<your_desired_view_corner_radius>"/>
</shape>
My map xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_margin="1dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/map_rounded_corner_overlay" />
</RelativeLayout>
I'm new on Android, and I am having trouble with the following:
I have an image of an empty test-tube (png or bmp).
And I need to draw lines on top of it to make the illusion that its being filled in with liquid.
I really don't know how to proceed. I have read google's documentation about animations, but that didn't help much.
I'd appreciate if you guys could give me some suggestions of how it can be done, and point to some tutorials/documentation that can help me.
Thanks in advance.
UPDATE:
The tube is not retangular, the bottom is oval.
I think I need to make the liquid fall into the test tube, then paint line by line, starting from the bottom. And I have to check for the borders of the tube (right and lef black pixels).
Any ideas of how this can be done?
UPDATE 2:
Here is the tube image: http://i61.tinypic.com/2nw0eb9.png
You can use a SurfaceView to draw what ever you want:
Basicly, you lock the surface's canvas by
Canvas canvas = mSurfaceView.getHolder().lockCanvas();
Then, use the canvas's methods to draw on it. canvas.drawBitmap, canvas.drawLine etc..
When you're finished lock the canvas with mSurfaceView.getHolder().unlockCanvasAndPost(canvas); and you're done.
here's an example from a quick google search:
http://android-coding.blogspot.co.il/2011/05/drawing-on-surfaceview.html
Best way to do this would be with a custom View. Make a new class, that extends View, then in its onDraw method first draw the picture, then draw your animations. If you want to do it by hand, you can do something like this:
private class TestTubeView extends View {
private int top = 0;
private Paint myPaint;
public MyView(Context context) {
super(context);
myPaint = new Paint();
myPaint.setColor(getResources().getColor(R.color.blue));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//First draw your bitmap
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.my_testtube), 0, 0, myPaint); //might need to use a different paint
//Then your "animation" as a static image, that has its position set from a variable, in this case "y" and "x"
canvas.drawRect(0, top, getWidth(), getHeight(), myPaint);
}
//In this method update your variables, that define the positions of your animated lines / bubbles
public boolean updateAnimation() {
top++;
invalidate();
//So it stops animationg
return top > getHeight();
}
}
Then in your layout you put it in like a normal view:
<com.example.TestTubeView
android:id="#+id/my_testtube"
android:layout_width="20dp"
android:layout_height="200dp" />
And then you animate it with a self-repeating Runnable:
final MyView testTube = findViewById(R.id.my_testtube);
final Handler myHandler = new Handler();
myHandler.post(new Runnable() {
#Override
public void run() {
if(testTube.updateAnimation()){
myHandler.postDelayed(this, 200);
}
}
});
You'll have to play around with sizes / heights and things like that though. Another way of doing this is with an ObjectAnimatior
Tube Drawable: (this is for test purposes. you will use your tube image)
tube.xml (drawable folder)
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<stroke android:width="5dp" android:color="#ffccffff" />
<solid android:color="#00000000" />
</shape>
tube_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" >
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/orangeJuiceLinearLayout"
android:layout_width="140dp"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:orientation="vertical"
android:background="#fff58225">
</LinearLayout>
<ImageView
android:id="#+id/tubeImageView"
android:layout_width="140dp"
android:layout_height="220dp"
android:layout_gravity="center"
android:background="#drawable/tube"
android:onClick="fillJuice"
android:clickable="true" />
</FrameLayout>
</LinearLayout>
TubeAcivity.java
public class TubeActivity extends Activity {
LinearLayout orangeLL;
ImageView tubeIV;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tube_activity);
orangeLL = (LinearLayout)findViewById(R.id.orangeJuiceLinearLayout);
tubeIV = (ImageView)findViewById(R.id.tubeImageView);
}
public void fillJuice(View view) {
ValueAnimator va = ValueAnimator.ofInt(0, tubeIV.getMeasuredHeight());
va.setDuration(1500);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
orangeLL.getLayoutParams().height = value.intValue();
orangeLL.requestLayout();
}
});
va.start();
}
}
I using custom switch for support of API 8. I am using THIS Libarary for Custom Switch. But I want to make something Like show in figure.I have tried to change the color ,though changing the color in the style but doesn't effect as i want.
Please help me , Thanks in advance.
Here's a full, working solution, after a fun day implementing this.
Use the following to set the drawable for the track of the switch. The track is the container within which the thumb slides left and right.
mMessengerSwitch.setTrackDrawable(new SwitchTrackTextDrawable(this,
"LEFT", "RIGHT"));
Here's the implementation of the SwitchTrackTextDrawable, which writes the text in the background exactly in the right position (well, I've only tested it for API 23 on a Nexus 5):
/**
* Drawable that generates the two pieces of text in the track of the switch, one of each
* side of the positions of the thumb.
*/
public class SwitchTrackTextDrawable extends Drawable {
private final Context mContext;
private final String mLeftText;
private final String mRightText;
private final Paint mTextPaint;
public SwitchTrackTextDrawable(#NonNull Context context,
#StringRes int leftTextId,
#StringRes int rightTextId) {
mContext = context;
// Left text
mLeftText = context.getString(leftTextId);
mTextPaint = createTextPaint();
// Right text
mRightText = context.getString(rightTextId);
}
private Paint createTextPaint() {
Paint textPaint = new Paint();
//noinspection deprecation
textPaint.setColor(mContext.getResources().getColor(android.R.color.white));
textPaint.setAntiAlias(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextAlign(Paint.Align.CENTER);
// Set textSize, typeface, etc, as you wish
return textPaint;
}
#Override
public void draw(Canvas canvas) {
final Rect textBounds = new Rect();
mTextPaint.getTextBounds(mRightText, 0, mRightText.length(), textBounds);
// The baseline for the text: centered, including the height of the text itself
final int heightBaseline = canvas.getClipBounds().height() / 2 + textBounds.height() / 2;
// This is one quarter of the full width, to measure the centers of the texts
final int widthQuarter = canvas.getClipBounds().width() / 4;
canvas.drawText(mLeftText, 0, mLeftText.length(),
widthQuarter, heightBaseline,
mTextPaint);
canvas.drawText(mRightText, 0, mRightText.length(),
widthQuarter * 3, heightBaseline,
mTextPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter cf) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Try using
android:textOn="On"
android:textOff="Off"
instead of
android:text="On"
in switches.
You can also go through this if it helps.
After struggling to find the right solution for this, I found this neat little library. I found it easy to use and it met my needs perfectly. It can even be used to display more than 2 values.
UPDATE: In the meanwhile this library has stopped being maintained, so you may want to try the one they recommend.
This is how I made it look eventually with some more styling, like white border which I put around a FrameLayout that wraps it (I needed to make it look exactly like this, you need not use border):
Here's the xml for this:
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="1dp"
android:background="#drawable/white_border">
<belka.us.androidtoggleswitch.widgets.ToggleSwitch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:activeBgColor="#color/white"
custom:activeTextColor="#color/black"
custom:inactiveBgColor="#color/black"
custom:inactiveTextColor="#color/white"
custom:textToggleLeft="left"
custom:textToggleRight="right"/>
</FrameLayout>
And #drawable/white_border looks like this:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="#android:color/transparent" />
<stroke android:width="2dip"
android:color="#color/white" />
<corners
android:radius="3dp"/>
I created a custom layout that contain a linear layout (will be used as a track of the switch) in this layout I placed two texts to simulate the track "on"/"off" texts, and on top of it, it has a regular switch but without a track, just a thumb with transparent track.
Anyway this is the code:
colors.xml
<color name="switch_selected_text_color">#FFFFFF</color>
<color name="switch_regular_text_color">#A8A8A8</color>
settings_switch_color_selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/switch_selected_text_color" android:state_checked="true" />
<item android:color="#color/switch_regular_text_color" />
</selector>
styles.xml
<style name="SwitchTextAppearance" parent="#android:style/TextAppearance.Holo.Small">
<item name="android:textColor">#color/settings_switch_color_selector</item>
</style>
new_switch.xml - used in the custom view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/track_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/settings_track"
android:weightSum="1">
<TextView
android:id="#+id/left_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:textColor="#color/switch_regular_text_color"
android:layout_weight="0.5"
android:gravity="center"
android:text="OFF" />
<TextView
android:id="#+id/right_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:textColor="#color/switch_regular_text_color"
android:layout_weight="0.5"
android:gravity="center"
android:text="ON" />
</LinearLayout>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:thumb="#drawable/thumb_selector"
android:switchTextAppearance="#style/SwitchTextAppearance"
android:textOn="ON"
android:textOff="OFF"
android:checked="true"
android:showText="true"
android:track="#android:color/transparent"/>
</RelativeLayout>
this is custom view - it`s just for inflating the custom view layout
public class DoubleSidedSwitch extends RelativeLayout {
private TextView _leftTextView;
private TextView _rightTextView;
private Switch _switch;
public DoubleSidedSwitch(Context context) {
super(context);
init(context);
}
public DoubleSidedSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
View view = LayoutInflater.from(context).inflate(R.layout.new_switch, this, true);
initViews(view);
initListeners();
}
private void initListeners() {
}
private void initViews(View view) {
}
}
There is one made by 2 Standard buttons and a LinearLayout. There are bunch of xml files to import but it works perfect on all versions and very easy to use. Check the following Github Page
Custom Switch With 2 Buttons
usage
Copy XML files under res/drawable to your project's res/drawable folder.
Copy LinearLayout from layout.xml to your layout file.
Copy values from values/colors.xml and values/dimens to your own files.
Initilize the switch with following code
SekizbitSwitch mySwitch = new SekizbitSwitch(findViewById(R.id.sekizbit_switch));
mySwitch.setOnChangeListener(new SekizbitSwitch.OnSelectedChangeListener() {
#Override
public void OnSelectedChange(SekizbitSwitch sender) {
if(sender.getCheckedIndex() ==0 )
{
System.out.println("Left Button Selected");
}
else if(sender.getCheckedIndex() ==1 )
{
System.out.println("Right Button Selected");
}
}
});
I have ListView with rounded corners implemented like bellow.
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/list_bg"
android:divider="#dbdbdb"
android:dividerHeight="1sp"
android:cacheColorHint="#00000000"
android:descendantFocusability="afterDescendants"
android:scrollbars="vertical">
</ListView>
where list_bg.xml is:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#ffffff"/>
<corners android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp"
/>
</shape>
This give me rounded corners.
The problem is that each items are RelativeLayout with TextView on the left side and ImageButton on the right side. Both views have custom graphic on pressed, foucused and default state. Something like this.
+------------------------------------+
| TextView | ImageButton |
+------------------------------------+
The problem is that custom graphic cover ListView background and thus rounded corners on first and last item. Everything is okay when scrolling.
I've written custom adapter and in getView method I set proper bg for items.
#Override
public View getView(int position, View view, ViewGroup parent) {
...
if(position == 0) {
mItemView.setBackgroundResource(R.drawable.cell_item_first_selector);
mImgBtn.setBackgroundResource(R.drawable.cell_btn_first_selector);
}
else if (position == mData.size() -1) {
mItemView.setBackgroundResource(R.drawable.cell_item_last_selector);
mImgBtn.setBackgroundResource(R.drawable.cell_btn_last_selector);
}
else {
mItemView.setBackgroundResource(R.drawable.cell_item_def_selector);
mImgBtn.setBackgroundResource(R.drawable.cell_btn_def_selector);
}
...
}
It works, but I'm not sure that is good solution. Is there any other way to automatically round corners in Android Views, not just by setting background?
public class ClippedListView extends ListView {
/**
* #param context
*/
public ClippedListView(Context context) {
super(context);
}
/**
* #param context
* #param attrs
*/
public ClippedListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void dispatchDraw(Canvas canvas) {
float radius = 10.0f;
Path clipPath = new Path();
RectF rect = new RectF(0, 0, this.getWidth(), this.getHeight());
clipPath.addRoundRect(rect, radius, radius, Path.Direction.CW);
canvas.clipPath(clipPath);
super.dispatchDraw(canvas);
}
}
First clipping was not working, then using
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
on my clippedListView solve the issue. My items' background are not messing with my corners anymore!
Well, if you need this background for the items in the list, you should attach the background to the item layout, and not to the list view.
I need to paint an overlay image onto a SeekBar in my ListView rows. This isn't too hard to do and the code in this question accomplishes that by painting the row number in a bitmap and placing it as the progress drawable.
The problem that I am having is that this only works one time. When the view is recycled to be used for another row, the drawable disappears.
The first 10 views (positions 0 through 9) worked. Everything after that fails. There is no error or exception. Android just stops drawing that layer. If I scroll down and then back up, none of them work (as they have all been recycled).
How do I get Android to always draw this overlayed image? I imagine there is some sort of caching or invalidation that I need to perform, but I just don't know what it is yet.
public class ViewTest extends ListActivity {
// replace the progress drawable with my custom drawable
public void setSeekBarOverlay(SeekBar seekBar, String overlayText) {
Bitmap bitmap = Bitmap.createBitmap(100, 12, Bitmap.Config.ARGB_4444);
new Canvas(bitmap).drawText(overlayText, 10, 12, new Paint());
Drawable d = new BitmapDrawable(bitmap);
((LayerDrawable) seekBar.getProgressDrawable()).setDrawableByLayerId(android.R.id.progress, d);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setListAdapter(new ListAdapter() {
// return the view. Recycle them if possible.
public View getView(int pos, View v, ViewGroup vg) {
if (v == null) {
v = View.inflate(ViewTest.this, R.layout.seekbar, null);
}
String s = String.valueOf(pos);
((TextView) v.findViewById(android.R.id.text1)).setText(s);
setSeekBarOverlay((SeekBar) v.findViewById(R.id.seekbar), s);
return v;
}
... cut ...
});
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
seekbar.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight">
<TextView
android:id="#android:id/text1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_height="wrap_content" />
<SeekBar
android:id="#+id/seekbar"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
</LinearLayout>
This is a tough one that I don't think will get much traffic, so I'm going to answer it myself since I found the solution.
Another SO question/answer, Android ProgressBar.setProgressDrawable only works once?, got me halfway there but the solution didn't work for me. In the other question, the code was trying to replace the entire progressDrawable (setProgressDrawable()). I'm not doing that here as I'm just updating a layer in the existing progressDrawable. After some playing around, with similar approaches, I found something that worked. You have to get the bounds of the drawable that is being updated (in this case, one of the layers) and set the bounds later.
Seems like a bug in the platform, but this workaround seems to make it work. Hope this helps someone else someday.
public void setSeekBarOverlay(SeekBar seekBar, String overlayText) {
LayerDrawable layerDrawable = (LayerDrawable) seekBar.getProgressDrawable();
Drawable overlayDrawable = layerDrawable.findDrawableByLayerId(android.R.id.progress);
Rect bounds = overlayDrawable.getBounds();
Bitmap bitmap = Bitmap.createBitmap(100, 12, Bitmap.Config.ARGB_4444);
new Canvas(bitmap).drawText(overlayText, 10, 12, new Paint());
overlayDrawable = new BitmapDrawable(bitmap);
overlayDrawable.setBounds(bounds);
layerDrawable.setDrawableByLayerId(android.R.id.progress, overlayDrawable);
}