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>
Related
I have use class RadioButtonCenter at
https://github.com/pizza/MaterialTabs/blob/master/sample/src/io/karim/materialtabs/sample/ui/RadioButtonCenter.java
It worked good in below API 24. But in API 24, It not display correct.
when i click tab new. tab old always active. When I intent to activity diff and back. It display correct.
I did not find the problem. Please. Help me!
This is xml
<RadioGroup
android:id="#+id/bottom_navigation_on_main"
android:layout_width="match_parent"
android:layout_height="#dimen/item_height"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:weightSum="4">
<RadioButtonCenter
android:id="#+id/tab_home"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#drawable/selector_tab_main"
android:button="#null"
android:clickable="true"
android:gravity="center"
app:radioDrawable="#drawable/selector_tab_home" />
<RadioButtonCenter
android:id="#+id/tab_search"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:button="#null"
android:clickable="true"
android:background="#drawable/selector_tab_main"
android:gravity="center"
app:radioDrawable="#drawable/selector_tab_search" />
</RadioGroup>
This is selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/ic_home_on" android:state_checked="true"
/>
<item android:drawable="#drawable/ic_home_on" android:state_activated="true"
/>
<item android:drawable="#drawable/ic_home_off" />
</selector>
I found the problem. I add requestLayout(); and now It worked !
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (buttonDrawable != null) {
if (Build.VERSION.SDK_INT >= 24){
requestLayout();
}
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);
}
}
I want to develop following screen in Android.
I used CircleLayout but I am still not able to achieve desired output. See following code and screenshot.
<com.example.hl.CircleLayout
android:id="#+id/pie"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
custom:dividerWidth="5dp"
custom:innerCircle="#drawable/profile_pic_icon"
custom:innerRadius="50dp"
custom:layoutMode="pie"
custom:sliceDivider="#android:color/transparent" >
<RelativeLayout
android:id="#+id/appt_center_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/appt_center_bg" >
<TextView
android:id="#+id/one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:onClick="onClick"
android:text="APP CENTER"
android:textStyle="bold" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/meds_cabinet_bg" >
<TextView
android:id="#+id/two"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="onClick"
android:text="MEDS CABINET"
android:textStyle="bold" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/check_in_bg" >
<TextView
android:id="#+id/three"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="onClick"
android:text="CHECK-IN"
android:textStyle="bold" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/my_tracker_bg" >
<TextView
android:id="#+id/four"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="onClick"
android:text="MY TRACKERS"
android:textStyle="bold" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/myaccount_bg" >
<TextView
android:id="#+id/five"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="onClick"
android:text="MY ACCOUNTS"
android:textStyle="bold" />
</RelativeLayout>
</com.example.hl.CircleLayout>
screenshot
Question:-
Is there any other library that can help developing the desired screen?
How to develop such screen using custom view? I mean what are steps to develop such custom view easily?
I have implemented a library for circular layout. Currently under development, basically meets need I think. Feel free to fork and develop.
https://github.com/ycagri/CircularLayout
End of Edit
You can use a custom layout given below. Number of items, inner radius and outer radius are defined in class. You can use those variables as custom layout attribute. The layout given below draws android launcher icon in the middle and around the circles. Titles are drawn below selection items.
Screenshot belongs to Nexus 7 device. Extra margin and padding can be defined to get better results on different screen resolutions.
public class CircleLayout extends View {
private final static int TOTAL_DEGREE = 360;
private final static int START_DEGREE = -90;
private Paint mPaint;
private RectF mOvalRect = null;
private int mItemCount = 5;
private int mSweepAngle;
private int mInnerRadius;
private int mOuterRadius;
private Bitmap mCenterIcon;
private int[] mColors = {Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE, Color.CYAN};
private String[] mTitles = {"APPT CENTER", "MEDS CABINET", "CHECK-IN", "MY TRACKERS", "MY ACCOUNTS"};
public CircleLayout(Context context) {
this(context, null);
}
public CircleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(2);
mSweepAngle = TOTAL_DEGREE / mItemCount;
mInnerRadius = 125;
mOuterRadius = 400;
mCenterIcon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
}
#Override
protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
if (mOvalRect == null) {
mOvalRect = new RectF(width / 2 - mOuterRadius, height / 2 - mOuterRadius, width / 2 + mOuterRadius, height / 2 + mOuterRadius);
}
for (int i = 0; i < mItemCount; i++) {
int startAngle = START_DEGREE + i * mSweepAngle;
mPaint.setColor(mColors[i]);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawArc(mOvalRect, startAngle, mSweepAngle, true, mPaint);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawArc(mOvalRect, startAngle, mSweepAngle, true, mPaint);
int centerX = (int) ((mOuterRadius + mInnerRadius) / 2 * Math.cos(Math.toRadians(startAngle + mSweepAngle / 2)));
int centerY = (int) ((mOuterRadius + mInnerRadius) / 2 * Math.sin(Math.toRadians(startAngle + mSweepAngle / 2)));
canvas.drawBitmap(mCenterIcon, width / 2 + centerX - mCenterIcon.getWidth() / 2, height / 2 + centerY - mCenterIcon.getHeight() / 2, null);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText(mTitles[i], width / 2 + centerX - mCenterIcon.getWidth() / 2, height / 2 + centerY + mCenterIcon.getHeight(), mPaint);
}
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(width / 2, height / 2, mInnerRadius, mPaint);
canvas.drawBitmap(mCenterIcon, width / 2 - mCenterIcon.getWidth() / 2, height / 2 - mCenterIcon.getHeight() / 2, null);
super.onDraw(canvas);
}
}
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" />
I want to display TEXT and Icon on a Button.
+----------------------------+
| Icon TEXT |
+----------------------------+
I tried with
<Button
android:id="#+id/Button01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="40dip"
android:text="TEXT"
android:drawableLeft="#drawable/Icon" />
But Text and Icon is not in center.
My Text size varies, according to text size Icon and Text should get adjusted to center.
How should i do it?
You can fake it by making a more complex layout, but I'm not sure whether it's worth it. Here's something I hacked together:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_alignTop="#+id/foreground"
android:layout_alignBottom="#id/foreground"
android:layout_alignRight="#id/foreground"
android:layout_alignLeft="#id/foreground"
android:onClick="clickedMe" />
<RelativeLayout
android:id="#id/foreground"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/button_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="#string/hello" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="#id/button_text"
android:paddingTop="10dip"
android:paddingBottom="10dip"
android:src="#drawable/icon" />
</RelativeLayout>
</RelativeLayout>
There might be a more concise way to do it. I tend to struggle getting RelativeLayout to do what I want sometimes. Note that you need to pay attention to the z-order (Button needs to appear first in the top level RelativeLayout) and you might need to adjust padding to get it to look the way you want.
Similar to some other approaches, I think a good solution is to extend Button and add the missing functionality by overriding its onLayout method:
public class CenteredIconButton extends Button {
private static final int LEFT = 0, TOP = 1, RIGHT = 2, BOTTOM = 3;
// Pre-allocate objects for layout measuring
private Rect textBounds = new Rect();
private Rect drawableBounds = new Rect();
public CenteredIconButton(Context context) {
this(context, null);
}
public CenteredIconButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.buttonStyle);
}
public CenteredIconButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!changed) return;
final CharSequence text = getText();
if (!TextUtils.isEmpty(text)) {
TextPaint textPaint = getPaint();
textPaint.getTextBounds(text.toString(), 0, text.length(), textBounds);
} else {
textBounds.setEmpty();
}
final int width = getWidth() - (getPaddingLeft() + getPaddingRight());
final Drawable[] drawables = getCompoundDrawables();
if (drawables[LEFT] != null) {
drawables[LEFT].copyBounds(drawableBounds);
int leftOffset =
(width - (textBounds.width() + drawableBounds.width()) + getRightPaddingOffset()) / 2 - getCompoundDrawablePadding();
drawableBounds.offset(leftOffset, 0);
drawables[LEFT].setBounds(drawableBounds);
}
if (drawables[RIGHT] != null) {
drawables[RIGHT].copyBounds(drawableBounds);
int rightOffset =
((textBounds.width() + drawableBounds.width()) - width + getLeftPaddingOffset()) / 2 + getCompoundDrawablePadding();
drawableBounds.offset(rightOffset, 0);
drawables[RIGHT].setBounds(drawableBounds);
}
}
}
The sample only works for left and right drawables, but could be extended to adjust top and bottom drawables too.
How about this one?
<?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"
android:background="#color/lovely_color"
android:clickable="true"
android:onClick="clickHandler">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="no?"
android:textColor="#color/white"
android:layout_centerHorizontal="true"
android:drawableLeft="#drawable/lovely_icon"
android:drawablePadding="10dp"
android:padding="10dp"
android:gravity="center"
android:textSize="21sp"/>
</RelativeLayout>
This should work
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<TextView
android:id="#+id/button_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="hello" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="10dip"
/>
</LinearLayout>
How about using a SpannableString as the text with an ImageSpan?
Button myButton = ...
SpannableString ss = new SpannableString(" " + getString(R.string.my_button_text));
Drawable d = getResources().getDrawable(R.drawable.myIcon);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan span = new ImageSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM);
ss.setSpan(span, 0, 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
myButton.setText(ss);
You can just set a padding depending on button size and image size:
Button button1 = null;
//initialize button….
ViewGroup.LayoutParams params = button1.getLayoutParams();
int btn1Width = ((int) (0.33 * (double)ecranWidth));
params.width = btn1Width;
button1.setLayoutParams(params);
button1.setPadding((btn1Width/2-9), 0, 0, 0);
//where (btn1Width/2-9) = size of button divided on 2 minux half size of icon…
The easy way (albeit not perfect) is to set the paddingRight to the same width as your icon.
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/your_text"
app:icon="#drawable/ic_example"
app:iconGravity="textStart"/>
This is what I did... It can be improved. The text is centered and the icon is to the left. So they both aren't centered as a group.
public class CustomButton extends Button
{
Rect r = new Rect();
private Drawable buttonIcon = null;
private int textImageSeparation = 10;
public CustomButton(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public CustomButton(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public CustomButton(Context context)
{
super(context);
}
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Drawable icon = getButtonIcon();
if(icon != null)
{
int drawableHeight = icon.getIntrinsicHeight();
int drawableWidth = icon.getIntrinsicWidth();
if(icon instanceof BitmapDrawable)
{
Bitmap bitmap = ((BitmapDrawable)icon).getBitmap();
drawableWidth = (int) AndroidScreenUtils.dipToPixels(bitmap.getWidth());
drawableHeight = (int) AndroidScreenUtils.dipToPixels(bitmap.getHeight());
}
else
{
drawableWidth = (int) AndroidScreenUtils.dipToPixels(icon.getIntrinsicWidth());
drawableHeight = (int) AndroidScreenUtils.dipToPixels(icon.getIntrinsicHeight());
}
float textWidth = getLayout().getPaint().measureText(getText().toString());
float left = ((getWidth() - textWidth) / 2) - getTextImageSeparation() - drawableWidth;
int height = getHeight();
int top = (height - drawableHeight) /2;
int right = (int) (left + drawableWidth);
int bottom = top + drawableHeight;
r.set((int) left, top, right, bottom);
icon.setBounds(r);
icon.draw(canvas);
}
}
private Drawable getButtonIcon()
{
return buttonIcon;
}
public void setButtonIcon(Drawable buttonIcon)
{
this.buttonIcon = buttonIcon;
}
private int getTextImageSeparation()
{
return textImageSeparation;
}
public void setTextImageSeparation(int dips)
{
this.textImageSeparation = (int) AndroidScreenUtils.dipToPixels(dips);
}
}
<LinearLayout
style="#style/Sonnen.Raised.Button.Transparent.LightBlueBorder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:orientation="horizontal"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableLeft="#drawable/refresh"
android:drawablePadding="10dp"
android:drawableStart="#drawable/refresh"
android:gravity="center"
android:text="#string/generic_error_button_text"
android:textColor="#color/dark_sky_blue"
android:textSize="20sp"/>
</LinearLayout>
I made a custom component to solve this problem.
Component class:
class CustomImageButton #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes) {
init {
inflate(context, R.layout.custom_image_button, this)
// Load the styled attributes and set their properties
val typedArray = context.obtainStyledAttributes(
attrs,
R.styleable.CustomImageButton, defStyleAttr, 0
)
val src = typedArray?.getDrawable(R.styleable.CustomImageButton_cib_src)
val text = typedArray?.getText(R.styleable.CustomImageButton_cib_text)
val contentDescription = typedArray?.getText(R.styleable.CustomImageButton_cib_contentDescription)
ivIcon.setImageDrawable(src)
tvText.text = text
ivIcon.contentDescription = contentDescription
typedArray?.recycle()
}
}
Component XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:toos="http://schemas.android.com/tools"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="fill_parent"
android:layout_height="#dimen/button_height">
<Button
android:id="#+id/bClick"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_alignTop="#+id/foreground"
android:layout_alignBottom="#id/foreground"
android:layout_alignEnd="#id/foreground"
android:layout_alignStart="#id/foreground"/>
<RelativeLayout
android:id="#id/foreground"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tvText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#color/textWhite"
toos:text="Some text to test"
toos:ignore="RelativeOverlap"/>
<ImageView
android:id="#+id/ivIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="#id/tvText"
android:paddingTop="1dip"
android:paddingBottom="1dip"
android:src="#mipmap/some_image_to_test"
toos:ignore="ContentDescription"/>
</RelativeLayout>
</RelativeLayout>
The resources attributes, attrs.xml:
<declare-styleable name="CustomImageButton">
<attr name="cib_src" format="reference"/>
<attr name="cib_text" format="string"/>
<attr name="cib_contentDescription" format="string"/>
</declare-styleable>
Component use example:
<app.package.components.CustomImageButton
android:id="#+id/cibMyImageButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:cib_src="#mipmap/my_image_to_put_in_the_button"
app:cib_text="Some text to show in the button"
app:cib_contentDescription="icon description"/>
This is a hack, but worked for me with a negative margin:
<Button
android:id="#+id/some_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="#drawable/some_drawable"
android:drawablePadding="-118dp"
android:paddingEnd="28dp"
android:text="#string/some_string" />
A better way would probably be doing this in a custom view
android:layout_gravity="center_vertical|center_horizontal|center" >
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>