How to make a vertical SeekBar in Android? - android

Can a SeekBar be vertical? I am not very good at UI design, so how can I make the SeekBar more beautiful, please give me some templates and examples.

For API 11 and later, can use seekbar's XML attributes(android:rotation="270") for vertical effect.
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rotation="270"/>
For older API level (ex API10), only use Selva's answer:
https://github.com/AndroSelva/Vertical-SeekBar-Android

Here is a very good implementation of vertical seekbar.
Have a look.
http://560b.sakura.ne.jp/android/VerticalSlidebarExample.zip
And Here is my own implementation for Vertical and Inverted Seekbar based on this
https://github.com/AndroSelva/Vertical-SeekBar-Android
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(),0);
super.onDraw(c);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
int i=0;
i=getMax() - (int) (getMax() * event.getY() / getHeight());
setProgress(i);
Log.i("Progress",getProgress()+"");
onSizeChanged(getWidth(), getHeight(), 0, 0);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}

Working example
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class VerticalSeekBar extends SeekBar {
public VerticalSeekBar(Context context) {
super(context);
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public VerticalSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
}
#Override
public synchronized void setProgress(int progress) // it is necessary for calling setProgress on click of a button
{
super.setProgress(progress);
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
onSizeChanged(getWidth(), getHeight(), 0, 0);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
}
There, paste the code and save it. Now use it in your XML layout:
<android.widget.VerticalSeekBar
android:id="#+id/seekBar1"
android:layout_width="wrap_content"
android:layout_height="200dp"
/>
Make sure to create a package android.widget and create VerticalSeekBar.java under this package

Try:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rotation="270"
/>
</RelativeLayout>

We made a vertical SeekBar by using android:rotation="270":
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/camera_sv_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:id="#+id/camera_lv_expose"
android:layout_width="32dp"
android:layout_height="200dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_marginRight="15dp"
android:orientation="vertical">
<TextView
android:id="#+id/camera_tv_expose"
android:layout_width="32dp"
android:layout_height="20dp"
android:textColor="#FFFFFF"
android:textSize="15sp"
android:gravity="center"/>
<FrameLayout
android:layout_width="32dp"
android:layout_height="180dp"
android:orientation="vertical">
<SeekBar
android:id="#+id/camera_sb_expose"
android:layout_width="180dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:rotation="270"/>
</FrameLayout>
</LinearLayout>
<TextView
android:id="#+id/camera_tv_help"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:text="#string/camera_tv"
android:textColor="#FFFFFF" />
</RelativeLayout>
Screenshot for camera exposure compensation:

I used Selva's solution but had two kinds of issues:
OnSeekbarChangeListener did not work properly
Setting progress programmatically did not work properly.
I fixed these two issues. You can find the solution (within my own project package) at
https://github.com/jeisfeld/Augendiagnose/blob/master/AugendiagnoseIdea/augendiagnoseLib/src/main/java/de/jeisfeld/augendiagnoselib/components/VerticalSeekBar.java

This worked for me, just put it into any layout you want to.
<FrameLayout
android:layout_width="32dp"
android:layout_height="192dp">
<SeekBar
android:layout_width="192dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:rotation="270" />
</FrameLayout>

Wrap it inside a FrameLayout so that there is no Size issue.
<FrameLayout
android:layout_width="#dimen/_20dp"
android:layout_marginStart="#dimen/_15dp"
android:layout_marginEnd="#dimen/_15dp"
android:layout_height="match_parent"
android:orientation="vertical">
<SeekBar
android:layout_width="150dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:rotation="270" />
</FrameLayout>

Note, it appears to me that if you change the width the thumb width does not change correctly.
I didn't take the time to fix it right, i just fixed it for my case. Here is what i did.
Couldn't figure out how to contact the original creator.
public void setThumb(Drawable thumb) {
if (thumb != null) {
thumb.setCallback(this);
// Assuming the thumb drawable is symmetric, set the thumb offset
// such that the thumb will hang halfway off either edge of the
// progress bar.
//This was orginally divided by 2, seems you have to adjust here when you adjust width.
mThumbOffset = (int)thumb.getIntrinsicHeight();
}

When moving the thumb with an EditText, the Vertical Seekbar setProgress may not work. The following code can help:
#Override
public synchronized void setProgress(int progress) {
super.setProgress(progress);
updateThumb();
}
private void updateThumb() {
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
This snippet code found here:
https://stackoverflow.com/a/33064140/2447726

Try this
import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.SeekBar;
/**
* Implementation of an easy vertical SeekBar, based on the normal SeekBar.
*/
public class VerticalSeekBar extends SeekBar {
/**
* The angle by which the SeekBar view should be rotated.
*/
private static final int ROTATION_ANGLE = -90;
/**
* A change listener registrating start and stop of tracking. Need an own listener because the listener in SeekBar
* is private.
*/
private OnSeekBarChangeListener mOnSeekBarChangeListener;
/**
* Standard constructor to be implemented for all views.
*
* #param context The Context the view is running in, through which it can access the current theme, resources, etc.
* #see android.view.View#View(Context)
*/
public VerticalSeekBar(final Context context) {
super(context);
}
/**
* Standard constructor to be implemented for all views.
*
* #param context The Context the view is running in, through which it can access the current theme, resources, etc.
* #param attrs The attributes of the XML tag that is inflating the view.
* #see android.view.View#View(Context, AttributeSet)
*/
public VerticalSeekBar(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
/**
* Standard constructor to be implemented for all views.
*
* #param context The Context the view is running in, through which it can access the current theme, resources, etc.
* #param attrs The attributes of the XML tag that is inflating the view.
* #param defStyle An attribute in the current theme that contains a reference to a style resource that supplies default
* values for the view. Can be 0 to not look for defaults.
* #see android.view.View#View(Context, AttributeSet, int)
*/
public VerticalSeekBar(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
protected final void onSizeChanged(final int width, final int height, final int oldWidth, final int oldHeight) {
super.onSizeChanged(height, width, oldHeight, oldWidth);
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
protected final synchronized void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
protected final void onDraw(#NonNull final Canvas c) {
c.rotate(ROTATION_ANGLE);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
public final void setOnSeekBarChangeListener(final OnSeekBarChangeListener listener) {
// Do not use super for the listener, as this would not set the fromUser flag properly
mOnSeekBarChangeListener = listener;
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
public final boolean onTouchEvent(#NonNull final MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStartTrackingTouch(this);
}
break;
case MotionEvent.ACTION_MOVE:
setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
break;
case MotionEvent.ACTION_UP:
setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStopTrackingTouch(this);
}
break;
case MotionEvent.ACTION_CANCEL:
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStopTrackingTouch(this);
}
break;
default:
break;
}
return true;
}
/**
* Set the progress by the user. (Unfortunately, Seekbar.setProgressInternally(int, boolean) is not accessible.)
*
* #param progress the progress.
* #param fromUser flag indicating if the change was done by the user.
*/
public final void setProgressInternally(final int progress, final boolean fromUser) {
if (progress != getProgress()) {
super.setProgress(progress);
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onProgressChanged(this, progress, fromUser);
}
}
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
public final void setProgress(final int progress) {
setProgressInternally(progress, false);
}
}

Getting started
Add these lines to build.gradle.
dependencies {
compile 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:0.7.2'
}
Usage
Java code
public class TestVerticalSeekbar extends AppCompatActivity {
private SeekBar volumeControl = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_vertical_seekbar);
volumeControl = (SeekBar) findViewById(R.id.mySeekBar);
volumeControl.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
int progressChanged = 0;
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
progressChanged = progress;
}
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
public void onStopTrackingTouch(SeekBar seekBar) {
Toast.makeText(getApplicationContext(), "seek bar progress:" + progressChanged,
Toast.LENGTH_SHORT).show();
}
});
}
}
Layout XML
<!-- This library requires pair of the VerticalSeekBar and VerticalSeekBarWrapper classes -->
<com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper
android:layout_width="wrap_content"
android:layout_height="150dp">
<com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar
android:id="#+id/mySeekBar"
android:layout_width="0dp"
android:layout_height="0dp"
android:max="100"
android:progress="0"
android:splitTrack="false"
app:seekBarRotation="CW90" /> <!-- Rotation: CW90 or CW270 -->
</com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper>
NOTE: android:splitTrack="false" is required for Android N+.

I tried in many different ways, but the one which worked for me was.
Use Seekbar inside FrameLayout
<FrameLayout
android:id="#+id/VolumeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/MuteButton"
android:layout_below="#id/volumeText"
android:layout_centerInParent="true">
<SeekBar
android:id="#+id/volume"
android:layout_width="500dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:progress="50"
android:secondaryProgress="40"
android:progressDrawable="#drawable/seekbar_volume"
android:secondaryProgressTint="#color/tint_neutral"
android:thumbTint="#color/tint_neutral"
/>
And in Code.
Setup Pre Draw callback on Seekbar, Where you can change the Width and height of the Seekbar
I did this part in c#, so Code i used was
var volumeSlider = view.FindViewById<SeekBar>(Resource.Id.home_link_volume);
var volumeFrameLayout = view.FindViewById<FrameLayout>(Resource.Id.linkVolumeFrameLayout);
void OnPreDrawVolume(object sender, ViewTreeObserver.PreDrawEventArgs e)
{
volumeSlider.ViewTreeObserver.PreDraw -= OnPreDrawVolume;
var h = volumeFrameLayout.Height;
volumeSlider.Rotation = 270.0f;
volumeSlider.LayoutParameters.Width = h;
volumeSlider.RequestLayout();
}
volumeSlider.ViewTreeObserver.PreDraw += OnPreDrawVolume;
Here i Add listener to PreDraw Event and when its triggered, I remove the PreDraw so that it doesnt go into Infinite loop.
So when Pre Draw gets executed, I fetch the Height of FrameLayout and assign it to Seekbar. And set the rotation of seekbar to 270.
As my seekbar is inside frame Layout and its Gravity is set as Center. I dont need to worry about the Translation. As Seekbar always stay in middle of Frame Layout.
Reason i remove EventHandler is because seekbar.RequestLayout(); Will make this event to be executed again.

You can do it by yourself - it's now so difficult.
Here is an example from my project: https://github.com/AlShevelev/WizardCamera
Let start from settings (attrs.xml).
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpositionBar">
<attr name="button_icon" format="reference" />
<attr name="button_icon_size" format="dimension" />
<attr name="stroke_width" format="dimension" />
<attr name="stroke_color" format="color" />
<attr name="button_color" format="color" />
<attr name="button_color_pressed" format="color" />
<attr name="min_value" format="float" />
<attr name="max_value" format="float" />
</declare-styleable>
</resources>
Here is a couple of utility functions:
fun <T: Comparable<T>>T.fitInRange(range: Range<T>): T =
when {
this < range.lower -> range.lower
this > range.upper -> range.upper
else -> this
}
fun Float.reduceToRange(rangeFrom: Range<Float>, rangeTo: Range<Float>): Float =
when {
this == rangeFrom.lower -> rangeTo.lower
this == rangeFrom.upper -> rangeTo.upper
else -> {
val placeInRange = (this - rangeFrom.lower) / (rangeFrom.upper - rangeFrom.lower)
((rangeTo.upper - rangeTo.lower) * placeInRange) + rangeTo.lower
}
}
And at last, but not least - a class for vertical seek bar:
class ExpositionBar
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val drawingRect = RectF(0f, 0f, 0f, 0f)
private val drawingPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val strokeWidth: Float
#ColorInt
private val strokeColor: Int
#ColorInt
private val buttonFillColor: Int
#ColorInt
private val buttonFillColorPressed: Int
private val icon: VectorDrawable
private val valuesRange: Range<Float>
private var centerX = 0f
private var minY = 0f
private var maxY = 0f
private var buttonCenterY = 0f
private var buttonRadiusExt = 0f
private var buttonRadiusInt = 0f
private var buttonMinY = 0f
private var buttonMaxY = 0f
private var buttonCenterBoundsRange = Range(0f, 0f)
private var iconTranslationX = 0f
private var iconTranslationY = 0f
private var isInDragMode = false
private var onValueChangeListener: ((Float) -> Unit)? = null
private var oldOutputValue = Float.MIN_VALUE
init {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpositionBar)
icon = typedArray.getDrawable(R.styleable.ExpositionBar_button_icon) as VectorDrawable
val iconSize = typedArray.getDimensionPixelSize(R.styleable.ExpositionBar_button_icon_size, 0)
icon.setBounds(0, 0, iconSize, iconSize)
strokeWidth = typedArray.getDimensionPixelSize(R.styleable.ExpositionBar_stroke_width, 0).toFloat()
drawingPaint.strokeWidth = strokeWidth
strokeColor = typedArray.getColor(R.styleable.ExpositionBar_stroke_color, Color.WHITE)
buttonFillColor = typedArray.getColor(R.styleable.ExpositionBar_button_color, Color.BLACK)
buttonFillColorPressed = typedArray.getColor(R.styleable.ExpositionBar_button_color_pressed, Color.BLUE)
val minValue = typedArray.getFloat(R.styleable.ExpositionBar_min_value, 0f)
val maxValue = typedArray.getFloat(R.styleable.ExpositionBar_max_value, 0f)
valuesRange = Range(minValue, maxValue)
typedArray.recycle()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
drawingRect.right = width.toFloat()
drawingRect.bottom = height.toFloat()
buttonCenterY = drawingRect.centerY()
recalculateDrawingValues()
}
override fun onDraw(canvas: Canvas) {
drawingPaint.color = strokeColor
drawingPaint.style = Paint.Style.STROKE
// Draw the center line
canvas.drawLine(centerX, minY, centerX, buttonMinY, drawingPaint)
canvas.drawLine(centerX, buttonMaxY, centerX, maxY, drawingPaint)
// Draw the button
canvas.drawCircle(centerX, buttonCenterY, buttonRadiusExt, drawingPaint)
drawingPaint.style = Paint.Style.FILL
drawingPaint.color = if(isInDragMode) buttonFillColorPressed else buttonFillColor
canvas.drawCircle(centerX, buttonCenterY, buttonRadiusInt, drawingPaint)
// Draw button icon
canvas.translate(iconTranslationX, iconTranslationY)
icon.draw(canvas)
canvas.translate(-iconTranslationX, -iconTranslationY)
}
#SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if(!isEnabled) {
return false
}
when(event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if(isButtonHit(event.y)){
isInDragMode = true
invalidate()
}
}
MotionEvent.ACTION_MOVE -> {
if(isInDragMode) {
buttonCenterY = event.y.fitInRange(buttonCenterBoundsRange)
recalculateDrawingValues()
invalidate()
val outputValue = buttonCenterY.reduceToRange(buttonCenterBoundsRange, valuesRange)
if (outputValue != oldOutputValue) {
onValueChangeListener?.invoke(outputValue)
oldOutputValue = outputValue
}
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
isInDragMode = false
invalidate()
}
}
return true
}
fun setOnValueChangeListener(listener: ((Float) -> Unit)?) {
onValueChangeListener = listener
}
private fun recalculateDrawingValues() {
centerX = drawingRect.left + drawingRect.width()/2
minY = drawingRect.top
maxY = drawingRect.bottom
buttonRadiusExt = drawingRect.width() / 2 - strokeWidth / 2
buttonRadiusInt = buttonRadiusExt - strokeWidth / 2
buttonMinY = buttonCenterY - buttonRadiusExt
buttonMaxY = buttonCenterY + buttonRadiusExt
val buttonCenterMinY = minY + buttonRadiusExt + strokeWidth / 2
val buttonCenterMaxY = maxY - buttonRadiusExt - strokeWidth / 2
buttonCenterBoundsRange = Range(buttonCenterMinY, buttonCenterMaxY)
iconTranslationX = centerX - icon.bounds.width() / 2
iconTranslationY = buttonCenterY - icon.bounds.height() / 2
}
private fun isButtonHit(y: Float): Boolean {
return y >= buttonMinY && y <= buttonMaxY
}
}
You can use it as shown here:
<com.shevelev.wizard_camera.main_activity.view.widgets.ExpositionBar
android:id="#+id/expositionBar"
android:layout_width="#dimen/mainButtonSize"
android:layout_height="300dp"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="#dimen/marginNormal"
android:layout_marginBottom="26dp"
app:button_icon = "#drawable/ic_brightness"
app:button_icon_size = "#dimen/toolButtonIconSize"
app:stroke_width = "#dimen/strokeWidthNormal"
app:stroke_color = "#color/mainButtonsForeground"
app:button_color = "#color/mainButtonsBackground"
app:button_color_pressed = "#color/mainButtonsBackgroundPressed"
app:min_value="-100"
app:max_value="100"
/>
Voila!

Simple answer
Instead of using android:rotation="270" inside of a seek bar, use it inside of a FrameLayout that wraps around it.
<FrameLayout
android:background="#color/gray"
android:layout_width="300dp"
android:layout_height="5dp"
android:layout_marginEnd="-126dp"
android:rotation="270"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<SeekBar
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
To get my frame layout to be 24dp margin right I calculated width -150dp + 24dp because the frame layout is first drawn horizontally and then rotated vertically.

In my case, I used an ordinary seekBar and just flipped out the layout.
seekbark_layout.xml - my layout that containts seekbar which we need to make vertical.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SeekBar
android:id="#+id/seekBar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.vgfit.seekbarexample.MainActivity">
<View
android:id="#+id/headerView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#color/colorAccent"/>
<View
android:id="#+id/bottomView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:background="#color/colorAccent"/>
<include
layout="#layout/seekbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/bottomView"
android:layout_below="#id/headerView"/>
</RelativeLayout>
And in MainActivity I rotate seekbar_layout:
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.RelativeLayout
import kotlinx.android.synthetic.main.seekbar_layout.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rootView.post {
val w = rootView.width
val h = rootView.height
rootView.rotation = 270.0f
rootView.translationX = ((w - h) / 2).toFloat()
rootView.translationY = ((h - w) / 2).toFloat()
val lp = rootView.layoutParams as RelativeLayout.LayoutParams
lp.height = w
lp.width = h
rootView.requestLayout()
}
}
}
As a result we have necessary vertical seekbar:

By using RotateLayout, having vertical SeekBar is a breeze. Just wrap that horrible SeekBar into it and Bob is your uncle:
<com.github.rongi.rotate_layout.layout.RotateLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:angle="-90"
>
<androidx.appcompat.widget.AppCompatSeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.github.rongi.rotate_layout.layout.RotateLayout>
https://github.com/rongi/rotate-layout

Hidden Vertical SeekBar
In a simpler way, you can make a SeekBar like this.
Like the way to increase or decrease the volume in video players. All
three Last attributes can be manipulated in the SeekBar.
<LinearLayout
android:layout_width="#dimen/_40dp"
android:layout_height="wrap_content"
android:layout_marginVertical="100dp"
android:gravity="center"
android:orientation="vertical"
>
<SeekBar
android:layout_width="500dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:rotation="270"
android:secondaryProgress="6"
android:progress="15"
android:progressDrawable="#null"
android:thumbTint="#null"
android:secondaryProgressTint="#null"
/>
</LinearLayout>

Related

Scroll behavior coordinatorLayout custom view

I need this type of behavior to implement.Image should be scroll and set into center with text like wtsapp. but in wtsapp it set into left alignment, i need to set into center. how can i achieve this?
after scrolled image will show like that with text in toolbar.(mentioned)
1. Behavior for CoordinatorLayout and AppBarLayout
public class AvatarImageBehavior extends CoordinatorLayout.Behavior<ImageView> {
// calculated from given layout
private int startXPositionImage;
private int startYPositionImage;
private int startHeight;
private int startToolbarHeight;
private boolean initialised = false;
private float amountOfToolbarToMove;
private float amountOfImageToReduce;
private float amountToMoveXPosition;
private float amountToMoveYPosition;
// user configured params
private float finalToolbarHeight, finalXPosition, finalYPosition, finalHeight;
private boolean onlyVerticalMove;
public AvatarImageBehavior(
final Context context,
final AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AvatarImageBehavior);
finalXPosition = a.getDimension(R.styleable.AvatarImageBehavior_finalXPosition, 0);
finalYPosition = a.getDimension(R.styleable.AvatarImageBehavior_finalYPosition, 0);
finalHeight = a.getDimension(R.styleable.AvatarImageBehavior_finalHeight, 0);
finalToolbarHeight = a.getDimension(R.styleable.AvatarImageBehavior_finalToolbarHeight, 0);
onlyVerticalMove = a.getBoolean(R.styleable.AvatarImageBehavior_onlyVerticalMove, false);
a.recycle();
}
}
#Override
public boolean layoutDependsOn(#NotNull final CoordinatorLayout parent, #NotNull final ImageView child, #NotNull final View dependency) {
return dependency instanceof AppBarLayout; // change if you want another sibling to depend on
}
#Override
public boolean onDependentViewChanged(#NotNull final CoordinatorLayout parent, #NotNull final ImageView child, #NotNull final View dependency) {
// make child (avatar) change in relation to dependency (toolbar) in both size and position, init with properties from layout
initProperties(child, dependency);
// calculate progress of movement of dependency
float currentToolbarHeight = startToolbarHeight + dependency.getY(); // current expanded height of toolbar
// don't go below configured min height for calculations (it does go passed the toolbar)
currentToolbarHeight = Math.max(currentToolbarHeight, finalToolbarHeight);
final float amountAlreadyMoved = startToolbarHeight - currentToolbarHeight;
final float progress = 100 * amountAlreadyMoved / amountOfToolbarToMove; // how much % of expand we reached
// update image size
final float heightToSubtract = progress * amountOfImageToReduce / 100;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = (int) (startHeight - heightToSubtract);
lp.height = (int) (startHeight - heightToSubtract);
child.setLayoutParams(lp);
// update image position
final float distanceXToSubtract = progress * amountToMoveXPosition / 100;
final float distanceYToSubtract = progress * amountToMoveYPosition / 100;
float newXPosition = startXPositionImage - distanceXToSubtract;
//newXPosition = newXPosition < endXPosition ? endXPosition : newXPosition; // don't go passed end position
if (!onlyVerticalMove) {
child.setX(newXPosition);
}
child.setY(startYPositionImage - distanceYToSubtract);
return true;
}
private void initProperties(
final ImageView child,
final View dependency) {
if (!initialised) {
// form initial layout
startHeight = child.getHeight();
startXPositionImage = (int) child.getX();
startYPositionImage = (int) child.getY();
startToolbarHeight = dependency.getHeight();
// some calculated fields
amountOfToolbarToMove = startToolbarHeight - finalToolbarHeight;
amountOfImageToReduce = startHeight - finalHeight;
amountToMoveXPosition = startXPositionImage - finalXPosition;
amountToMoveYPosition = startYPositionImage - finalYPosition;
initialised = true;
}
}
}
```java
public class AppBarScrollWatcher implements AppBarLayout.OnOffsetChangedListener {
private int scrollRange = -1;
private OffsetListener listener;
public AppBarScrollWatcher(OffsetListener listener) {
this.listener = listener;
}
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (scrollRange == -1) {
scrollRange = appBarLayout.getTotalScrollRange();
}
int appbarHeight = scrollRange + verticalOffset;
float alpha = (float) appbarHeight / scrollRange;
if (alpha < 0) {
alpha = 0;
}
float alphaZeroOnCollapsed = shrinkAlpha(alpha);
float alphaZeroOnExpanded = Math.abs(alphaZeroOnCollapsed - 1);
int argbZeroOnExpanded = (int) Math.abs((alphaZeroOnCollapsed * 255) - 255);
int argbZeroOnCollapsed = (int) Math.abs(alphaZeroOnCollapsed * 255);
listener.onAppBarExpanding(alphaZeroOnExpanded <= 0, alphaZeroOnCollapsed <= 0, argbZeroOnExpanded, argbZeroOnCollapsed, alphaZeroOnCollapsed, alphaZeroOnExpanded);
}
private float shrinkAlpha(float alpha) {
NumberFormat formatter = NumberFormat.getInstance(Locale.getDefault());
formatter.setMaximumFractionDigits(2);
formatter.setMinimumFractionDigits(2);
formatter.setRoundingMode(RoundingMode.HALF_DOWN);
return Float.parseFloat(formatter.format(alpha));
}
public interface OffsetListener {
void onAppBarExpanding(boolean expanded, boolean collapsed, int argbZeroOnExpanded, int argbZeroOnCollapsed, float alphaZeroOnCollapsed, float alphaZeroOnExpanded);
}
}
res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AvatarImageBehavior">
<attr name="finalXPosition" format="dimension" />
<attr name="finalYPosition" format="dimension" />
<attr name="finalHeight" format="dimension" />
<attr name="finalToolbarHeight" format="dimension" />
<attr name="onlyVerticalMove" format="boolean" />
</declare-styleable>
</resources>
2. Implementation in Activity/Fragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentScrim="#color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:titleEnabled="false">
<LinearLayout
android:id="#+id/header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/holo_orange_light"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingStart="24dp"
android:paddingTop="160dp"
android:paddingEnd="24dp"
android:paddingBottom="56dp">
</LinearLayout>
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:layout_gravity="bottom"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:titleMarginStart="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<ImageView
android:id="#+id/still_photo"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:contentDescription="#string/app_name"
android:scaleType="fitCenter"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_ph_person_male_80dp" />
<ImageView
android:id="#+id/ic_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.7"
android:clickable="true"
android:contentDescription="#string/app_name"
android:focusable="true"
android:padding="8dp"
android:tint="#android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_more_vert_black_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="#+id/v_sections"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:behavior_overlapTop="24dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<View
android:layout_width="match_parent"
android:layout_height="1000dp" />
</androidx.cardview.widget.CardView>
</androidx.core.widget.NestedScrollView>
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/moving_photo"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="64dp"
android:contentDescription="#string/app_name"
android:scaleType="fitCenter"
app:finalHeight="48dp"
app:finalToolbarHeight="?android:attr/actionBarSize"
app:finalYPosition="4dp"
app:layout_behavior=".custom.AvatarImageBehavior"
app:onlyVerticalMove="true"
app:srcCompat="#drawable/ic_ph_person_male_80dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
private lateinit var appBarScrollListener: AppBarScrollWatcher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_launcher)
setupAppBar()
}
private fun setupAppBar() {
appBarScrollListener =
AppBarScrollWatcher(AppBarScrollWatcher.OffsetListener { _, collapsed, _, _, _, _ ->
still_photo.visibility = if (collapsed) View.VISIBLE else View.INVISIBLE
})
app_bar.addOnOffsetChangedListener(appBarScrollListener)
}
override fun onDestroy() {
app_bar.removeOnOffsetChangedListener(appBarScrollListener)
super.onDestroy()
}
Note that you should put two ImageView in the layout.
AppCompatImageView directly inside the CoordinatorLayout so that we can
use CoordinatorLayout.Behavior on it, it would be the moving photo.
The important prop here is app:onlyVerticalMove="true", that make
your moving photo scrolled vertically. I made the default value to
false, it will move the photo to the start point of CoordinatorLayout
(top left).
Put another ImageView inside the Appbar layout as the final photo displayed
in the Appbar. Init this with invisible state, then use AppBarLayout behavior to show the photo when the collapsing toolbar is being collapsed.
If you want to exclude Toolbar from moving elements, just remove android:layout_gravity="bottom"

how to set android:layout_centerInParent="true" attribute in custom view

i am continuously searching and trying to sort out a issue in custom view and Relative layout.
i found multiple solution but only few of solution are usefull,but they not fulfilling my requirement completely.
partial solution 1.
partial solution 2.
via these solution i can manage the height and width of my custom view, but i just want to align my custom view in center of parent RelativeLayout,
code which i am trying throw hit-and-run is below. ;)
JAVA CODE
public class MyActiveView extends View {
public Movie mMovie;
public long movieStart;
private int gifId;
public MyActiveView(Context context, AttributeSet attrs) {
super(context, attrs);
initializeView();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth / 2, parentHeight);
}
private void initializeView() {
InputStream is = getContext().getResources().openRawResource(R.raw.pj_logo1);
mMovie = Movie.decodeStream(is);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
super.onDraw(canvas);
long now = android.os.SystemClock.uptimeMillis();
if (movieStart == 0) {
movieStart = now;
}
if (mMovie != null) {
int relTime = (int) ((now - movieStart) % mMovie.duration());
mMovie.setTime(relTime);
mMovie.draw(canvas, getWidth() - mMovie.width(), getHeight() - mMovie.height());
this.invalidate();
}
}
public int getActiveResource() {
return this.gifId;
}
public void setActiveResource(int resId) {
this.gifId = resId;
initializeView();
}
}
XML CODE
<?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">
<ImageView
android:id="#+id/backsourceImagesplash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="#drawable/bg_blur" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#color/colorTransparentWhite" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="256dp"
android:layout_centerVertical="true"
android:gravity="centre">
<pj.com.pjlib.activity_base.support_class.MyActiveView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
i want to set my custom active view layout in centre of its parent Relative Layout, but stuked in it. any help will be appreciated.
check the alignment of both image,i am trying for attribute android:layout_centerInParent="true", according to Relative layout support the "congrats image" should be in center, but it's not happening.
so i am thinking,i missed something in my custom view class but what is that, i don't know.
Change android:gravity="centre" with android:gravity="center" in Relative Layout. I think this will helpful to you.

Android : Custom View not drawing

I'm getting a problem with a part of my Android application. I need to draw a graph in a custom Dialog box but it doesn't work even when I follow all the solutions found on the internet. My goal is to show a Dialog box when a user is clicking on a button on my main frame. To do this, I want to draw it in a very simple Dialog Box. My graph need to be "scrollable" because it can be bigger than the Dialog box. Here are the sources codes :
public class GraphDialog extends Dialog implements android.view.View.OnClickListener
{
Canvas canvas;
public GraphDialog(Context context)
{
super(context);
System.out.println("Test");
this.setContentView(R.layout.dialog_graph_layout);
this.setTitle(R.string.title_dialog_graph);
((Button) this.findViewById(R.id.button_ok)).setOnClickListener(this);
}
#Override
public void onClick(View v)
{
Controller.getPollManager().setPoll(null);
Controller.getPollManager().setTab(null);
this.cancel();
}
}
Here is my 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:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin" >
<ScrollView
android:id="#+id/scroll_layout"
android:layout_width="500px"
android:layout_height="800px"
android:layout_weight="1"
android:drawingCacheQuality="low"
android:scrollbars="vertical" >
<be.ac.ucl.lfsab1509.proxipoll.GraphView
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</be.ac.ucl.lfsab1509.proxipoll.GraphView>
</ScrollView>
<Button
android:id="#+id/button_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="0"
android:text="#string/ok" />
</LinearLayout>
And here is my custom View :
public class GraphView extends View
{
Paint paint;
public GraphView(Context context)
{
super(context);
System.out.println("test GraphView");
this.setWillNotDraw(false);
init();
}
public GraphView(Context context, AttributeSet attrs)
{
super(context, attrs);
System.out.println("test GraphView");
this.setWillNotDraw(false);
init();
}
public void init()
{
paint = new Paint();
paint.setColor(Color.BLACK);
}
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
System.out.println("test onDraw");
canvas.drawText(Controller.getPollManager().getPoll().name, 50, 50, paint);
this.draw(canvas);
}
}
To check what goes wrong, you can see that I have added some println(). When I try to show the Dialog box, I get all lines except the on of the onDraw() method. Does someone know what to do to make it work ?
Thank you
Try overriding onMeasure: when I last did any custom charting, I needed to do something like this.
#Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec )
{
viewWidth = MeasureSpec.getSize( widthMeasureSpec );
viewHeight = MeasureSpec.getSize( heightMeasureSpec );
if(box.getWidth() != 0){
// use the screen width: oldViewWidth -> screenWidth, viewWidth -> maxXRange
boxWidth = box.getWidth() - box.getPaddingRight() - box.getPaddingLeft() - 10;
if(screenWidth <= 0) viewWidth = boxWidth;
else viewWidth = (int)((getMaxXRange()*boxWidth)/screenWidth);
if(viewWidth < boxWidth) viewWidth = boxWidth;
}
setMeasuredDimension( viewWidth, viewHeight );
}
This basically checks the size of the window and tells the graphics layer how big I want it to be.

Aligning drawableLeft with text of button

Here is my layout:
The issue I'm facing is with the drawable checkmark. How would I go about aligning it next to the text, both of them centered within the button? Here is the XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PostAssignmentActivity" >
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal" >
<Button
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableLeft="#drawable/ic_checkmark_holo_light"
android:text="Post" />
<Button
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cancel" />
</LinearLayout>
</RelativeLayout>
Applying android:gravity="center_vertical" pulls the text and drawable together, but then the text is no longer aligned in the center.
Solution 1
Set android:paddingLeft inside your first button. This will force the drawableLeft by paddingLeft amount to the right. This is the fast/hacky solution.
Solution 2
Instead of using a ButtonView, use a LinearLayout that contains both a textview and imageview. This is a better solution. It gives you more flexibility in the positioning of the checkmark.
Replace your ButtonView with the following code. You need the LinearLayout and TextView to use buttonBarButtonStyle so that the background colors are correct on selection and the text size is correct. You need to set android:background="#0000" for the children, so that only the LinearLayout handles the background coloring.
<LinearLayout
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal" >
<ImageView
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:background="#0000"
android:src="#drawable/ic_checkmark_holo_light"/>
<TextView
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:background="#0000"
android:text="Done" />
</LinearLayout>
Here are some screenshots I took while trying this out.
None of these solutions worked correctly without presenting unacceptable trade-offs (create a layout with views in it? Not a good idea). So why not roll your own? This is what I got:
First create an attrs.xml with this:
<resources>
<declare-styleable name="IconButton">
<attr name="iconSrc" format="reference" />
<attr name="iconSize" format="dimension" />
<attr name="iconPadding" format="dimension" />
</declare-styleable>
</resources>
This allows to create an icon with specific size, padding from text, and image in our new view. The view code looks like this:
public class IconButton extends Button {
private Bitmap mIcon;
private Paint mPaint;
private Rect mSrcRect;
private int mIconPadding;
private int mIconSize;
public IconButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public IconButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public IconButton(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
int shift = (mIconSize + mIconPadding) / 2;
canvas.save();
canvas.translate(shift, 0);
super.onDraw(canvas);
if (mIcon != null) {
float textWidth = getPaint().measureText((String)getText());
int left = (int)((getWidth() / 2f) - (textWidth / 2f) - mIconSize - mIconPadding);
int top = getHeight()/2 - mIconSize/2;
Rect destRect = new Rect(left, top, left + mIconSize, top + mIconSize);
canvas.drawBitmap(mIcon, mSrcRect, destRect, mPaint);
}
canvas.restore();
}
private void init(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.IconButton);
for (int i = 0; i < array.getIndexCount(); ++i) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.IconButton_iconSrc:
mIcon = drawableToBitmap(array.getDrawable(attr));
break;
case R.styleable.IconButton_iconPadding:
mIconPadding = array.getDimensionPixelSize(attr, 0);
break;
case R.styleable.IconButton_iconSize:
mIconSize = array.getDimensionPixelSize(attr, 0);
break;
default:
break;
}
}
array.recycle();
//If we didn't supply an icon in the XML
if(mIcon != null){
mPaint = new Paint();
mSrcRect = new Rect(0, 0, mIcon.getWidth(), mIcon.getHeight());
}
}
public static Bitmap drawableToBitmap (Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
And then it can be used like this:
<com.example.grennis.myapplication.IconButton
android:layout_width="200dp"
android:layout_height="64dp"
android:text="Delete"
app:iconSrc="#android:drawable/ic_delete"
app:iconSize="32dp"
app:iconPadding="6dp" />
This works for me.
You can use
<com.google.android.material.button.MaterialButton/> .
https://material.io/develop/android/components/material-button/
It finally allows setting the icon gravity.
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:text="Awesome button"
app:icon="#drawable/your_icon"
app:iconGravity="textStart" />
Here is a clean easy way, without doing anything fancy, to achieve the results of having a Button that is much wider than the content with Image and Text which are centered.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:background="#drawable/button_background_selector">
<Button
android:layout_centerInParent="true"
android:gravity="center"
android:duplicateParentState="true"
android:layout_width="wrap_content"
android:text="New User"
android:textSize="15sp"
android:id="#android:id/button1"
android:textColor="#android:color/white"
android:drawablePadding="6dp"
android:drawableLeft="#drawable/add_round_border_32x32"
android:layout_height="64dp" />
</RelativeLayout>
In our case, we wanted to use the default Button class (to inherit its various styles and behaviors) and we needed to be able to create the button in code. Also, in our case we could have text, an icon (left drawable), or both.
The goal was to center the icon and/or text as a group when the button width was wider than wrap_content.
public class CenteredButton extends Button
{
public CenteredButton(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
// We always want our icon and/or text grouped and centered. We have to left align the text to
// the (possible) left drawable in order to then be able to center them in our onDraw() below.
//
setGravity(Gravity.LEFT|Gravity.CENTER_VERTICAL);
}
#Override
protected void onDraw(Canvas canvas)
{
// We want the icon and/or text grouped together and centered as a group.
// We need to accommodate any existing padding
//
float buttonContentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
// In later versions of Android, an "all caps" transform is applied to buttons. We need to get
// the transformed text in order to measure it.
//
TransformationMethod method = getTransformationMethod();
String buttonText = ((method != null) ? method.getTransformation(getText(), this) : getText()).toString();
float textWidth = getPaint().measureText(buttonText);
// Compute left drawable width, if any
//
Drawable[] drawables = getCompoundDrawables();
Drawable drawableLeft = drawables[0];
int drawableWidth = (drawableLeft != null) ? drawableLeft.getIntrinsicWidth() : 0;
// We only count the drawable padding if there is both an icon and text
//
int drawablePadding = ((textWidth > 0) && (drawableLeft != null)) ? getCompoundDrawablePadding() : 0;
// Adjust contents to center
//
float bodyWidth = textWidth + drawableWidth + drawablePadding;
canvas.translate((buttonContentWidth - bodyWidth) / 2, 0);
super.onDraw(canvas);
}
}
Here is my code and working perfect.
<Button
android:id="#+id/button"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:background="#drawable/green_btn_selector"
android:gravity="left|center_vertical"
android:paddingLeft="50dp"
android:drawableLeft="#drawable/plus"
android:drawablePadding="5dp"
android:text="#string/create_iou"
android:textColor="#color/white" />
public class DrawableCenterTextView extends TextView {
public DrawableCenterTextView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public DrawableCenterTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DrawableCenterTextView(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
Drawable[] drawables = getCompoundDrawables();
if (drawables != null) {
Drawable drawableLeft = drawables[0];
Drawable drawableRight = drawables[2];
if (drawableLeft != null || drawableRight != null) {
float textWidth = getPaint().measureText(getText().toString());
int drawablePadding = getCompoundDrawablePadding();
int drawableWidth = 0;
if (drawableLeft != null)
drawableWidth = drawableLeft.getIntrinsicWidth();
else if (drawableRight != null) {
drawableWidth = drawableRight.getIntrinsicWidth();
}
float bodyWidth = textWidth + drawableWidth + drawablePadding;
canvas.translate((getWidth() - bodyWidth) / 2, 0);
}
}
super.onDraw(canvas);
}
}
This is now available in the Material Button by default with the app:iconGravity property. However, the Material Button does not allow for setting the background to a drawable (RIP gradients).
I converted the answers by #BobDickinson and #David-Medenjak above to kotlin and it works great.
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.Gravity
import androidx.appcompat.widget.AppCompatButton
import kotlin.math.max
class CenteredButton #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = R.attr.buttonStyle
) : AppCompatButton(context, attrs, defStyle) {
init {
gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
}
override fun onDraw(canvas: Canvas) {
val buttonContentWidth = (width - paddingLeft - paddingRight).toFloat()
var textWidth = 0f
layout?.let {
for (i in 0 until layout.lineCount) {
textWidth = max(textWidth, layout.getLineRight(i))
}
}
val drawableLeft = compoundDrawables[0]
val drawableWidth = drawableLeft?.intrinsicWidth ?: 0
val drawablePadding = if (textWidth > 0 && drawableLeft != null) compoundDrawablePadding else 0
val bodyWidth = textWidth + drawableWidth.toFloat() + drawablePadding.toFloat()
canvas.save()
canvas.translate((buttonContentWidth - bodyWidth) / 2, 0f)
super.onDraw(canvas)
canvas.restore()
}
}
I know it's a bit late, but if anyone looking for another answer, here is another way to add icon without the need to wrap button with a ViewGroup
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="#+id/btnCamera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click!"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
*need to set textAllCaps to false to make the spannable working
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonLabelBuilder = SpannableStringBuilder(btnCamera.text)
val iconDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_camera)
iconDrawable?.setBounds(0, 0, btnCamera.lineHeight, btnCamera.lineHeight)
val imageSpan = ImageSpan(iconDrawable, ImageSpan.ALIGN_BOTTOM)
buttonLabelBuilder.insert(0, "i ")
buttonLabelBuilder.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
btnCamera.text = buttonLabelBuilder
}
}
I started with #BobDickinson's answer, but it did not cope well with multiple lines. The approach is good, because you still end up with a Button that can properly be reused.
Here is an adapted solution that will also work if the button has multiple lines (Please don't ask why.)
Just extend Button and use the following in onDraw, the getLineRight() is used to look up the actual length of each line.
#Override
protected void onDraw(Canvas canvas) {
// We want the icon and/or text grouped together and centered as a group.
// We need to accommodate any existing padding
final float buttonContentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
float textWidth = 0f;
final Layout layout = getLayout();
if (layout != null) {
for (int i = 0; i < layout.getLineCount(); i++) {
textWidth = Math.max(textWidth, layout.getLineRight(i));
}
}
// Compute left drawable width, if any
Drawable[] drawables = getCompoundDrawables();
Drawable drawableLeft = drawables[0];
int drawableWidth = (drawableLeft != null) ? drawableLeft.getIntrinsicWidth() : 0;
// We only count the drawable padding if there is both an icon and text
int drawablePadding = ((textWidth > 0) && (drawableLeft != null)) ? getCompoundDrawablePadding() : 0;
// Adjust contents to center
float bodyWidth = textWidth + drawableWidth + drawablePadding;
canvas.save();
canvas.translate((buttonContentWidth - bodyWidth) / 2, 0);
super.onDraw(canvas);
canvas.restore();
}
Here is a another solution:
<LinearLayout
android:id="#+id/llButton"
android:layout_width="match_parent"
style="#style/button_celeste"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
style="#style/button_celeste"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="10dp"
android:clickable="false"
android:drawableLeft="#drawable/icon_phone"
android:text="#string/call_runid"/>
</LinearLayout>
and the event:
LinearLayout btnCall = (LinearLayout) findViewById(R.id.llButton);
btnCall.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
call(runid.Phone);
}
});
I had the same issue, and I've come up with a solution that doesn't require XML changes or custom Views.
This code snippet retrieves the width of the text and the left/right drawables, and sets the Button's left/right padding so there will only be enough space to draw the text and the drawables, and no more padding will be added.
This can be applied to Buttons as well as TextViews, their superclasses.
public class TextViewUtils {
private static final int[] LEFT_RIGHT_DRAWABLES = new int[]{0, 2};
public static void setPaddingForCompoundDrawableNextToText(final TextView textView) {
ViewTreeObserver vto = textView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
shinkRoomForHorizontalSpace(textView);
}
});
}
private static void shinkRoomForHorizontalSpace(TextView textView) {
int textWidth = getTextWidth(textView);
int sideCompoundDrawablesWidth = getSideCompoundDrawablesWidth(textView);
int contentWidth = textWidth + sideCompoundDrawablesWidth;
int innerWidth = getInnerWidth(textView);
int totalPadding = innerWidth - contentWidth;
textView.setPadding(totalPadding / 2, 0, totalPadding / 2, 0);
}
private static int getTextWidth(TextView textView) {
String text = textView.getText().toString();
Paint textPaint = textView.getPaint();
Rect bounds = new Rect();
textPaint.getTextBounds(text, 0, text.length(), bounds);
return bounds.width();
}
private static int getSideCompoundDrawablesWidth(TextView textView) {
int sideCompoundDrawablesWidth = 0;
Drawable[] drawables = textView.getCompoundDrawables();
for (int drawableIndex : LEFT_RIGHT_DRAWABLES) {
Drawable drawable = drawables[drawableIndex];
if (drawable == null)
continue;
int width = drawable.getBounds().width();
sideCompoundDrawablesWidth += width;
}
return sideCompoundDrawablesWidth;
}
private static int getInnerWidth(TextView textView) {
Rect backgroundPadding = new Rect();
textView.getBackground().getPadding(backgroundPadding);
return textView.getWidth() - backgroundPadding.left - backgroundPadding.right;
}
}
Notice that:
It actually still leaves some more space than needed (good enough for my purposes, but you may look for the error)
It overwrites whatever left/right padding is there. I guess it's not difficult to fix that.
To use it, just call TextViewUtils.setPaddingForCompoundDrawableNextToText(button) on your onCreate or onViewCreated().
There are several solutions to this problem. Perhaps the easiest on some devices is to use paddingRight and paddingLeft to move the image and text next to each other as below.
Original button
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="16dp"
android:text="#string/scan_qr_code"
android:textColor="#color/colorPrimary"
android:drawableLeft="#drawable/ic_camera"
android:paddingRight="90dp"
android:paddingLeft="90dp"
android:gravity="center"
/>
The problem here is on smaller devices this padding can cause unfortunate problems such as this:
The other solutions are all some version of "build a button out of a layout an image and a textview". They work, but completely emulating a button can be tricky. I propose one more solution; "build a button out of a layout an image, a textview, and a button"
Here's the same button rendered as I propose:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="16dp"
android:gravity="center"
>
<Button
android:id="#+id/scanQR"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/white_bg_button"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_centerInParent="true"
android:gravity="center"
android:elevation="10dp"
>
<ImageView
android:id="#+id/scanImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:src="#drawable/ic_camera"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/Base.TextAppearance.AppCompat.Button"
android:text="#string/scan_qr_code"
android:textColor="#color/colorPrimary"
/>
</LinearLayout>
</RelativeLayout>
As you can see, the button is now within a relative layout, but it's text and drawableLeft are not part of the button, they are in a separate layout that's placed on top of the button. With this, the button still acts like a button. The gotchas are:
The inner layout needs an elevation for newer versions of Android. The button itself has an elevation greater than the ImageView and TextView, so even though they are defined after the Button, they will still be "below" it in elevation and be invisible. Setting 'android:elevation' to 10 solves this.
The textAppearance of the TextView must be set so that it has the same appearance as it would in a button.
Another quite hacky alternative is to add blank spacer views with weight="1" on each side of the buttons. I don't know how this would affect performance.
<View
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />

android RadioButton button drawable gravity

I am generating RadioButtons dynamically with
RadioButton radioButton=new RadioButton(context);
LayoutParams layoutParams=new LayoutParams(radioWidth,radioHeight);
layoutParams.gravity=Gravity.CENTER;
radioButton.setLayoutParams(layoutParams);
radioButton.setGravity(Gravity.CENTER);
BitmapDrawable bitmap = ((BitmapDrawable)drawableResource);
bitmap.setGravity(Gravity.CENTER);
radioButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.itabs_radio));
radioButton.setButtonDrawable(bitmap);
as you can see I am desperately trying to set gravity of button drawable to center, but without a reason its always center and left aligned, heres the reason- the default style of android radio button:
<style name="Widget.CompoundButton">
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:textAppearance">?android:attr/textAppearance</item>
<item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
<item name="android:gravity">center_vertical|left</item>
</style>
<style name="Widget.CompoundButton.RadioButton">
<item name="android:background">#android:drawable/btn_radio_label_background</item>
<item name="android:button">#android:drawable/btn_radio</item>
</style>
Is there any way I can align button drawable to center?
According to CompoundButton.onDraw() source code it's always left-aligned.
(Note the line buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height);)
You will have to derive a new class from RadioButton and override onDraw().
EXAMPLE ADDED LATER:
Ok, so here's what you do. Firstly, here's a layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<org.test.TestProj.RadioButtonCenter
android:id="#+id/myview"
android:layout_width="fill_parent"
android:layout_height="100dp"
android:layout_centerInParent="true"
android:text="Button test"
/>
</RelativeLayout>
Secondly here's the custom-drawing RadioButtonCenter:
package org.test.TestProj;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.RadioButton;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
public class RadioButtonCenter extends RadioButton {
public RadioButtonCenter(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CompoundButton, 0, 0);
buttonDrawable = a.getDrawable(1);
setButtonDrawable(android.R.color.transparent);
}
Drawable buttonDrawable;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (buttonDrawable != null) {
buttonDrawable.setState(getDrawableState());
final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
final int height = buttonDrawable.getIntrinsicHeight();
int y = 0;
switch (verticalGravity) {
case Gravity.BOTTOM:
y = getHeight() - height;
break;
case Gravity.CENTER_VERTICAL:
y = (getHeight() - height) / 2;
break;
}
int buttonWidth = buttonDrawable.getIntrinsicWidth();
int buttonLeft = (getWidth() - buttonWidth) / 2;
buttonDrawable.setBounds(buttonLeft, y, buttonLeft+buttonWidth, y + height);
buttonDrawable.draw(canvas);
}
}
}
Finally, here's an attrs.xml file you need to put in res/values so the code can get at platform-defined attributes.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CompoundButton">
<attr name="android:button" />
</declare-styleable>
</resources>
Simple solution, you can add a background to RadioButton, or set background="#null", .
<RadioButton
android:id="#+id/cp_rd_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#null"/>
updated:
<RadioGroup
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#null"
android:button="#null"
android:drawableTop="#drawable/account_coolme_selector"
android:gravity="center" />
<RadioButton
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="#null"
android:button="#null"
android:drawableTop="#drawable/account_qq_selector"
android:gravity="center"
/>
</RadioGroup>
Based on #hoot answers, I had customised it to make both text and drawable to the center without using attars,
class RadioButtonCenter(context: Context, attrs: AttributeSet) : RadioButton(context, attrs) {
internal var buttonDrawable: Drawable? = null
init {
buttonDrawable = CompoundButtonCompat.getButtonDrawable(this#RadioButtonCenter)
}
override fun onDraw(canvas: Canvas) {
val iconHeight = buttonDrawable!!.intrinsicHeight
val buttonWidth = buttonDrawable!!.intrinsicWidth
val totalWidth =
buttonWidth + paint.measureText(text.toString()) + paddingLeft + paddingRight + compoundDrawablePadding
if (totalWidth >= width) {
super.onDraw(canvas)
} else {
setButtonDrawable(android.R.color.transparent)
val availableSpace = ((width - totalWidth) / 2).toInt()
buttonDrawable!!.state = drawableState
val height = height
var yTop = 0
val verticalGravity = gravity and Gravity.VERTICAL_GRAVITY_MASK
when (verticalGravity) {
Gravity.BOTTOM -> yTop = height - iconHeight
Gravity.CENTER_VERTICAL -> yTop = (height - iconHeight) / 2
}
var rightWidth = availableSpace + buttonWidth
buttonDrawable!!.setBounds(availableSpace, yTop, rightWidth, yTop + iconHeight)
buttonDrawable!!.draw(canvas)
rightWidth += compoundDrawablePadding
val yPos = (height / 2 - (paint.descent() + paint.ascent()) / 2) as Float
canvas.drawText(
text.toString(),
(rightWidth).toFloat(),
yPos,
paint
)
}
}
}
Based on #Reprator answers.
JAVA version:
public class RadioButtonCentered extends AppCompatRadioButton {
private Drawable buttonDrawable;
public RadioButtonCentered(Context context) {
super(context);
}
public RadioButtonCentered(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RadioButtonCentered(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas) {
if (buttonDrawable != null) {
int iconHeight = buttonDrawable.getIntrinsicHeight();
int buttonWidth = buttonDrawable.getIntrinsicWidth();
int width = getWidth();
float totalWidth = buttonWidth + getPaint().measureText(getText().toString()) + getPaddingLeft() + getPaddingRight() + getCompoundDrawablePadding();
if (totalWidth >= width) { super.onDraw(canvas); }
else {
int yTop = 0;
int height = getHeight();
int availableSpace = (int) ((width - totalWidth) / 2);
int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
int rightWidth = availableSpace + buttonWidth;
switch (verticalGravity) {
case Gravity.BOTTOM:
yTop = height - iconHeight;
break;
case Gravity.CENTER_VERTICAL:
yTop = (height - iconHeight) / 2;
break;
}
setButtonDrawable(android.R.color.transparent);
buttonDrawable.setState(getDrawableState());
buttonDrawable.setBounds(availableSpace, yTop, rightWidth, yTop + iconHeight);
buttonDrawable.draw(canvas);
float yPos = (height / 2 - (getPaint().descent() + getPaint().ascent()) / 2);
canvas.drawText(getText().toString(), ((float) (rightWidth + getCompoundDrawablePadding())), yPos, getPaint());
}
} else {buttonDrawable = CompoundButtonCompat.getButtonDrawable(this); invalidate();}
}
}
I also think this sounds like a bug since it's always left-aligned. In my case I solved the issue by setting android:minWidth="0dp" and android:layout_width="wrap_content", since Material components had set the android:minWidth to a width larger than the drawable width. If the RadioButton needs to be centered it can then be added to a container and thus no custom view needs to be implemented.
Here's an example of how it could look:
<FrameLayout
android:layout_width="200dp"
android:layout_height="200dp">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false"
android:minWidth="0dp" />
</FrameLayout>
However, be aware that the minimum width was set there for a reason, Material design used ?attr/minTouchTargetSize. So if you do like above, the container should maybe also be touchable.
<radiogroup android:paddingLeft = "20dp" android:background="#color/gray">
Basically - I have a horizontally aligned radio group, and by expanding the background color to the left 20dp (or whatever 1/2 of your width of radio button) it appears as if it's centered.
you need foreground. not background. see args for layout and set em programmatically:
<RadioButton>
...
android:button="#null"
android:foreground="#drawable/your_selector_for_center_drawable"
android:background="#drawable/your_selector_for_background_drawable"
android:foregroundGravity="center"
</RadioButton>

Categories

Resources