Emil Adz Emil Adz - 5 months ago 29
Android Question

How to design a paragraph style title in android:

I need to reach the following result, with dynamic titles that are coming from the server:

enter image description here

I tried to reach this result using the answer from the following question:

How to make the first character much larger than other in a TextView

But the result I reached was not good enough for the design team.
There are several requirements that are requested by the design:


  1. Big first latterv (B).

  2. the text continues a little bit above the middle of the big letter (the smaller red letters).

  3. The second row of text continue aligned with the base of the big letter ("and star in film").

  4. finally if there is more text it continue under the big letter.



The Question: What would be the best way to reach this result in Android?

Thanks in advance.

Answer

The solution would be to have 3 views, one for the first letter, another for the (maximum) two lines, and another for the remainder of the text. When measuring the view, you can determine if you've passed the 2 lines limit you wish to impose, and if so break the text and set the remainder of the text in the 3rd view. You would also need to overcome the internal font padding of the first letter in order to achieve your desired result, hence the BottomAlignedTextView view.

Here is the code:

BottomAlignedTextView.java

public class BottomAlignedTextView extends TextView {

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

 @Override
 protected void onDraw(Canvas canvas) {
     float offset = getTextSize() - getLineHeight();
     canvas.translate(0, -offset);
     super.onDraw(canvas);
 }

}

view_reader_title.xml

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

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.shellanoo.newsbot.ui.views.BottomAlignedTextView
            android:id="@+id/first_letter_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:fontFamily="serif"
            android:background="@null"
            android:gravity="bottom"
            android:includeFontPadding="false"
            android:textSize="92dp"
            tools:text="B"/>

        <TextView
            android:id="@+id/two_lines_tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@+id/first_letter_tv"
            android:layout_toEndOf="@+id/first_letter_tv"
            android:layout_toRightOf="@+id/first_letter_tv"
            android:gravity="bottom"
            android:includeFontPadding="false"
            android:lineSpacingMultiplier="0.9"
            android:textColor="@color/black"
            android:textSize="30dp"
            tools:text="eyonce to write and star in a film"/>
    </RelativeLayout>

    <TextView
        android:id="@+id/remainder_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="2dp"
        android:layout_marginStart="2dp"
        android:includeFontPadding="false"
        android:textColor="@color/black"
        android:textSize="30dp"
        tools:text="about Saartjie Baartman"/>
</LinearLayout>

ReaderTitleView.java

public class ReaderTitleView extends FrameLayout {

    @BindView(R.id.first_letter_tv)
    TextView firstLetterTv;

    @BindView(R.id.two_lines_tv)
    TextView twoLinesTv;

    @BindView(R.id.remainder_tv)
    TextView remainderTv;

    @ColorInt
    private int mFirstWordColor;
    private String mText;

    public ReaderTitleView(Context context) {
        this(context, null);
    }

    public ReaderTitleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ReaderTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        View view = inflate(getContext(), R.layout.view_reader_title, this);
        ButterKnife.bind(this, view);
        TypedArray a = getContext().getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.ReaderTitleView,
                0, 0);
        mText = a.getString(R.styleable.ReaderTitleView_rtv_text);
        if (mText == null) {
            mText = "";
        }
        mFirstWordColor = a.getColor(R.styleable.ReaderTitleView_rtv_first_word_color, -1);

        updateTextViews();
    }

    private void updateTextViews() {
        if (!TextUtils.isEmpty(mText)) {
            String firstLetter = mText.substring(0, 1);
            firstLetter = firstLetter.toUpperCase();
            String restText = mText.substring(1, mText.length());

            firstLetterTv.setText(firstLetter);
            twoLinesTv.setText(restText);
            colorifyFirstWord();
        } else {
            firstLetterTv.setText("");
            twoLinesTv.setText("");
            remainderTv.setText("");
        }
    }

    private void colorifyFirstWord() {
        if (mFirstWordColor != -1) {
            CharSequence text = twoLinesTv.getText();
            Spannable s;
            if (text instanceof Spannable) {
                s = (Spannable) text;
            } else {
                s = new SpannableString(text);
            }
            String[] split = s.toString().split(" ", 2);
            int start = 0;
            int end = start + split[0].length();
            s.setSpan(new ForegroundColorSpan(mFirstWordColor), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            twoLinesTv.setText(s, TextView.BufferType.SPANNABLE);
            firstLetterTv.setTextColor(mFirstWordColor);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (twoLinesTv.getLineCount() > 2) {
            String text = twoLinesTv.getText().toString();

            int secondLineEnd = twoLinesTv.getLayout().getLineEnd(1);
            String twoLines = text.substring(0, secondLineEnd);
            String remainder = text.substring(secondLineEnd, text.length());

            twoLinesTv.setText(twoLines);
            remainderTv.setText(remainder);
            colorifyFirstWord();
        }
    }

    public void setText(String text) {
        mText = text;
        updateTextViews();
    }

    public String getText() {
        return mText;
    }
    }