18
votes

I made a custom background for a button and also for different button states. But now I made to a point that I cannot understand.

When button is in normal state then it looks just fine. But when I press the button, I need to move text down few pixels because button background image moving (actually it feels like it moving on the image, because first there's border under the button and when it's in pressed state then this border disappears). Please see image below.

How can I move the buttons text in the button when buttons state is pressed? (maybe padding somehow or layout custom for a button)

enter image description here

5
you may try this for 3D button stackoverflow.com/a/19248175/2149195RBK
Addendum to the bounty: I'd now tried a bunch of stuff with 9 patches to get the text content to move. The most promising of which looked like android:variablePadding="true" but I'm still having no luck (and yea, I tried invalidating the view on touch events....)Sam

5 Answers

6
votes

Setting padding in 9-patch didn't work for me. Setting padding in touch listeners is messy, would litter code anywhere buttons are used.

I went with subclassing Button, and it turned out reasonably tidy. In my case, I wanted to offset icon (drawableLeft) and text 1px left and 1px down.

Subclassed button widget:

package com.myapp.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

import com.myapp.R;

public class OffsetButton extends Button {

    public OffsetButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public OffsetButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public OffsetButton(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean value = super.onTouchEvent(event);

        if (event.getAction() == MotionEvent.ACTION_UP) {
            setBackgroundResource(R.drawable.btn_normal);
        } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
            setBackgroundResource(R.drawable.btn_pressed);
            setPadding(getPaddingLeft() + 1, getPaddingTop() + 1, getPaddingRight() - 1,
                    getPaddingBottom() - 1);
        }

        return value;
    }
}

And use it in layout like this:

<com.myapp.widgets.OffsetButton
    android:text="@string/click_me"
    android:drawableLeft="@drawable/icon_will_be_offset_too_thats_good" />

Few notes:

  • I did not use StateListDrawable for background, and am instead switching backgrounds in code. When I tried using StateListDrawable, there would be small pause between padding change and background change. That didn't look good.

  • Setting background resets padding, so don't need to adjust padding in ACTION_UP case

  • It was important to increase top and left padding, and at the same time decrease bottom and right padding. So the size of content area stays the same and content area is effectively just shifted.

4
votes

I did not try it myself but if you use nine-patch as a background drawable for both states then you should consider setting proper padding box in pressed state drawable. See details here.

3
votes

You can use padding on the view. You can add an OnTouchListener to the button or view like

viewF.setOnTouchListener(new View.OnTouchListener() {  
    @Override 
    public boolean onTouch(View v, MotionEvent event) { 
        if (event.getAction == MotionEvent.ACTION_UP) { 
            //set your padding            
        } else if (event.getAction == MotionEvent.ACTION_DOWN){
            //set your padding            
        }   
        return true; 
    } 
}); 

The ontouchlistener will let your know when the button is pressed and not.

1
votes

The Pēteris Caune answer works worse than overriding setPressed(). But everithing OK with setPressed until you test your app at ICS or lower device and add button as listview item. To archieve this I've improved my button's class:

public class OffsetButton extends Button {

    private static final int OFFSET_IN_DP = 6;
    private int offset_in_px;
    private boolean wasPressed = false;
    private Integer[] defaultPaddings;

    public OffsetButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    public OffsetButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public OffsetButton(Context context) {
        super(context);
        initView();
    }

    private void initView() {
        offset_in_px = (int) DisplayUtil.convertDpToPixel(OFFSET_IN_DP);
    }

    @Override
    public void setPressed(boolean pressed) {
        if (pressed && !wasPressed) {
            changePaddings();
        }
        if (!pressed && wasPressed) {
            resetPaddings();
        }
        super.setPressed(pressed);
    }

    private void changePaddings() {
        defaultPaddings = new Integer[]{getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()};
        setPadding(getPaddingLeft(), getPaddingTop() + offset_in_px, getPaddingRight(), getPaddingBottom() - offset_in_px);
        wasPressed = true;
    }

    private void resetPaddings() {
        setPadding(defaultPaddings[0], defaultPaddings[1], defaultPaddings[2], defaultPaddings[3]);
        wasPressed = false;
    }

    @Override
    public boolean performClick() {
        resetPaddings();
        return super.performClick();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isEnabled())
        {
            if (event.getAction() == MotionEvent.ACTION_DOWN && !wasPressed) {
                changePaddings();
            } else if (event.getAction() == MotionEvent.ACTION_UP && wasPressed) {
                resetPaddings();
            }
        }
        return super.onTouchEvent(event);
    }
}
0
votes

this might just work:

setPadding(left, top, right, bottom); // Normal
setPadding(left, top + x, right, bottom - x); // Pressed