3

I want to create buttons that look like in the picture. Inside the circle (which is transparent in the png) I want to place the profile picture of players. There should also be text on the blue bar. I've got it working but it just seems way too complicated. I think it is easier to understand what I have done without giving code but if you need it I can add it. Here is the layout:

  • RelativeLayout
    • LinearLayout (horizontal orientation)
      • Empty view with weight 0.7
      • Profile Picture with weight 0.2
      • Empty view with weight 0.1
    • the overlay picture that I posted below
    • LinearLayout (horizontal orientation)
      • RelativeLayout with weight 0.7 (space where all the text can go)
      • empty view with weigh 0.3

enter image description here

By the way: to the right of the circle, the png isn't transparent but white!

This works well but there must be a better way! All these empty views just to align the picture to the right position is kind of ugly. And the fact that the overlay picture must go inbetween the profile picture and the text makes it even uglier.

I'd prefer to do it without a png as overlay but with simple shapes (so that it looks good on every screen) but I wouldn't know how to do that. Would you recommend that? And if yes, how could that be done?

Or do you have an idea how to improve the xml layout or how to do it otherwise.

Thanks very much

DominicM
  • 2,186
  • 5
  • 24
  • 42

3 Answers3

2

You can do it without any image:

Layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:weightSum="1.0">
    <TextView
        android:layout_weight="0.7"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:text="New Text"
        android:id="@+id/textView"
        android:background="#0073ff"/>
    <ImageView
        android:layout_weight="0.2"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:background="@drawable/half_round_drawable"
        android:src="@drawable/profile"/>
    </LinearLayout>
</LinearLayout>

half_round_drawable:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item>
        <shape android:shape="oval">
            <corners android:radius="16dp" />
            <solid android:color="#0073ff" />
        </shape>
    </item>
    <item
        android:bottom="0dp"
        android:right="32dp"> <!-- radius *2 -->
        <shape>
            <solid android:color="#0073ff" />
        </shape>
    </item>

</layer-list>

To make the profile-image round you should use something like this: How to create a circular ImageView in Android?

Community
  • 1
  • 1
Eun
  • 4,146
  • 5
  • 30
  • 51
1

You can use a simple LinearLayout if you confine the background image to the profile area at the right side. You can define the content area in the image itself if you use a nine-patch drawable, as follows:

  1. Extract the profile portion from your background image file.
  2. Create a nine patch drawable from it, defining all the area as stretchable (left and top border lines), and the empty circle as the content area (right and bottom lines).
  3. Since you should ideally have the image at the foreground layer to ensure that the photo isn't drawn outside of the circle, you can use a FrameLayout with a foreground drawable to contain your profile photo's ImageView. There would also need to be another dummy child view to work around a bug in FrameLayout that causes a single child with match_parent dimensions to be layout incorrectly.

This is how the layout should look like at the end:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/text"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="#00f" />

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:foreground="@drawable/profile_bg">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <ImageView
            android:id="@+id/photo"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
</LinearLayout>
corsair992
  • 3,050
  • 22
  • 33
  • If I've understood everything correctly this is exactly what I was looking for. May I ask you what you think of Eun's answer, which also seems to be very good. Is the only downside of that solution that it isn't as flexible with shapes as with 9 patches? So if I were a 100% sure that my design always stays so simple that it could be created with shapes, I'd take his solution? – DominicM Feb 07 '15 at 12:36
  • @DominicM: Eun's solution can work too, with size and padding defined in the shape drawable. The circle won't be transparent in that case so the foreground trick won't work there, but as he suggests you can use a `RoundedImageView`. It won't draw properly if it's scaled from it's default dimensions though. If that's an issue, it could still be solved by using a `clip` drawable instead of hardcoding the width/inset of the second shape and setting the drawable level to 5000 (50%) from the code. – corsair992 Feb 07 '15 at 13:58
  • @DominicM: Another option would be to do this in the layout itself, by adding negative margin and padding to the right in the `TextView` equaling the radius of the circle. Combined with a `RoundedImageView` with the appropriate border defined, this should also produce the desired result. This would also suffer from the same limitation as the `shape` drawable solution. – corsair992 Feb 07 '15 at 20:16
-2

Now I am ready to present my answer.

  • Portret:

    enter image description here

  • Landscape:

    enter image description here

Layout.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:shape="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <!--This is the CustomView which include -->
    <!--own attributes: -->
    <!--circle_radius is the radius of image, -->
    <!--content_padding is the padding,-->
    <!--and background_color is the color of shape.-->

    <CustomShape
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        shape:circle_radius="40dp"
        shape:content_padding="8dp"
        shape:background_color="#FF983493">

        <!--There must be two Views:-->
        <!--TextView and ImageView and only in this order.-->
        <!--Set-->
        <!--android:layout_width="wrap_content"-->
        <!--android:layout_height="wrap_content"-->
        <!--to bot of them, because in CustomShape it will be-->
        <!--resized for you. There also don`t need to set -->
        <!--any kind of margin or location attributes.-->

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/txt"
            android:padding="5dp"
            android:textColor="@android:color/white"
            android:text="sdsfkjsdkfhsdk flsdkfjkls asdfasd fklasdjl fkjasdklfjasd k "
            android:background="@android:color/transparent"/>

        <!--For RoundImage I use custom class which round the drawable,-->
        <!--not a View. Look down.-->

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/img"
            android:src="@drawable/img"
            android:scaleType="fitCenter"
            android:background="@android:color/transparent" />

    </CustomShape>

</RelativeLayout>

CustomShape class:

public class CustomShape extends RelativeLayout {
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

    int circleRadius; // image radius
    int diameter; // image diameter
    int contentPadding;
    int semiPadding;
    int rectRightSide;

    int backgroundColor;
    int viewWidth; // width of parent(CustomShape layout)

    public CustomShape(Context context) {
        super(context);
        this.setWillNotDraw(false);
    }

    public CustomShape(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomShape, 0, 0);
        try {
            this.circleRadius = (int) ta.getDimension(R.styleable.CustomShape_circle_radius, 40);
            this.contentPadding = (int) ta.getDimension(R.styleable.CustomShape_content_padding, 8);
            this.backgroundColor = ta.getColor(R.styleable.CustomShape_background_color, 0);

            this.semiPadding = contentPadding / 2;
            this.diameter = circleRadius * 2;
        } finally {
            ta.recycle();
        }

        this.setWillNotDraw(false);
    }

    public CustomShape(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setWillNotDraw(false);
    }

    @Override
    protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
        super.onSizeChanged(xNew, yNew, xOld, yOld);

        viewWidth = xNew;

        this.rectRightSide = viewWidth - circleRadius - (circleRadius / 2); // get position for image
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ImageView img = (ImageView) this.getChildAt(1);
        RelativeLayout.LayoutParams imgParams = new LayoutParams(diameter - contentPadding, diameter - contentPadding);
        imgParams.leftMargin = rectRightSide - circleRadius + semiPadding;
        imgParams.topMargin = semiPadding;
        img.setLayoutParams(imgParams);

        //Create custom RoundImage and set to image

        try {
            Drawable drawable = img.getDrawable();
            Bitmap bm = ((BitmapDrawable) drawable).getBitmap();
            RoundImage resultImage = new RoundImage(bm);
            img.setImageDrawable(resultImage);
        } catch (ClassCastException e) {
        }

        //Positioning and resizing TextView

        View txt = this.getChildAt(0);
        RelativeLayout.LayoutParams txtParams = new LayoutParams(rectRightSide - circleRadius - semiPadding, diameter - contentPadding);
        txtParams.topMargin = semiPadding;
        txtParams.leftMargin = semiPadding;
        txt.setLayoutParams(txtParams);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);

        this.setMeasuredDimension(parentWidth, diameter); // set correct height
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        paint.setColor(backgroundColor);
        canvas.drawRect(0, 0, rectRightSide, diameter, paint);

        //Draw circle

        paint.setDither(true);
        canvas.drawCircle(rectRightSide, circleRadius, circleRadius, paint);
    }
}

Attr.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomShape">
        <attr name="circle_radius" format="dimension" />
        <attr name="content_padding" format="dimension" />
        <attr name="background_color" format="color" />
    </declare-styleable>
</resources>

RoundImage class:

public class RoundImage extends Drawable {
    private final Bitmap mBitmap;
    private final Paint mPaint;
    private final RectF mRectF;
    private final int mBitmapWidth;
    private final int mBitmapHeight;

    public RoundImage(Bitmap bitmap) {
        mBitmap = bitmap;
        mRectF = new RectF();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mPaint.setShader(shader);

        mBitmapWidth = mBitmap.getWidth();
        mBitmapHeight = mBitmap.getHeight();
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawOval(mRectF, mPaint);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        mRectF.set(bounds);
    }

    @Override
    public void setAlpha(int alpha) {
        if (mPaint.getAlpha() != alpha) {
            mPaint.setAlpha(alpha);
            invalidateSelf();
        }
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public int getIntrinsicWidth() {
        return mBitmapWidth;
    }

    @Override
    public int getIntrinsicHeight() {
        return mBitmapHeight;
    }

    public void setAntiAlias(boolean aa) {
        mPaint.setAntiAlias(aa);
        invalidateSelf();
    }

    @Override
    public void setFilterBitmap(boolean filter) {
        mPaint.setFilterBitmap(filter);
        invalidateSelf();
    }

    @Override
    public void setDither(boolean dither) {
        mPaint.setDither(dither);
        invalidateSelf();
    }

    public Bitmap getBitmap() {
        return mBitmap;
    }

}

Hope it will help you.

GIGAMOLE
  • 1,274
  • 1
  • 11
  • 17
  • If you're using a custom view, then you might as well have it implement the whole drawing logic itself, instead of hardcoding a few tweaks on top of a standard container. All three of your defined attributes already exist or could be inferred from existing attributes. Also, why are you modifying layout attributes from the `onDraw()` method, and then calling `requestLayout()`? That would result in an infinite cycle of drawing and layout. – corsair992 Feb 07 '15 at 20:31
  • I can create if blabla. This is sample. And why is requestLayout, cause there are litle moves of this views at attaching to layout. Unfortunately this is wrong answer. – GIGAMOLE Feb 07 '15 at 20:51
  • But why are you changing the layout from inside `onDraw()`? This should ideally be moved to `onMeasure()`. The `onDraw()` method should be lightweight and have no side-effects, and as I mentioned, calling `requestLayout()` from there would result in an endless cycle. – corsair992 Feb 07 '15 at 20:56
  • I know. But when I am tried to put this into `onMeasure()` there nothing happened. – GIGAMOLE Feb 07 '15 at 21:02
  • You would need to change the layout params _before_ calling through to the super implementation of `onMeasure()` to have any effect. – corsair992 Feb 07 '15 at 21:05
  • You can remove the `requestLayout()` calls now, as `onMeasure()` is called _before_ layout. Also, honestly, all of this could have be done in the layout XML itself, using appropriate alignment and anchoring params. There was no need to create a custom subclass of `RelativeLayout`. – corsair992 Feb 07 '15 at 21:28
  • I`ll do this because this is like lib. – GIGAMOLE Feb 07 '15 at 21:28
  • What do you mean by "like lib"? – corsair992 Feb 07 '15 at 21:32
  • This is like: "I don`t need to think how can do this, I`ll just put attr, img and all. Ohh, thx." – GIGAMOLE Feb 07 '15 at 21:34
  • In that case, why did you bother to add an answer when there were already two existing answers? – corsair992 Feb 07 '15 at 21:39
  • I`d like to play with tasks like this. – GIGAMOLE Feb 07 '15 at 21:41
  • You seem to be contradicting yourself here, but OK, whatever you wish :p – corsair992 Feb 07 '15 at 21:45