nani/app/rubytextview/src/main/java/me/weilunli/views/RubyTextView.java

RubyTextView.java

1
package me.weilunli.views;
2
3
import android.content.Context;
4
import android.content.res.TypedArray;
5
import android.graphics.Canvas;
6
import android.graphics.Paint;
7
import android.os.Build;
8
import android.util.AttributeSet;
9
import android.util.TypedValue;
10
11
import androidx.appcompat.widget.AppCompatTextView;
12
13
import java.util.ArrayList;
14
import java.util.List;
15
16
public class RubyTextView extends AppCompatTextView {
17
18
    private Paint textPaint;
19
    private Paint rubyTextPaint;
20
    private String combinedText = "";
21
    private float rubyTextSize= 28f;
22
    private int rubyTextColor ;
23
    private float spacing = 0f;
24
    private float lineSpacingExtra;
25
26
    // Need to address first line because it don't need extra spacing.
27
    private float lineheight = 0;
28
    private float firstLineheight = 0;
29
    StringBuilder originalText;
30
    List<String[]> combinedTextArray;
31
32
33
    public RubyTextView(Context context) {
34
        super(context);
35
        initialize();
36
        setValue();
37
    }
38
39
    public RubyTextView(Context context, AttributeSet attrs) {
40
        super(context, attrs);
41
42
        initialize();
43
44
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.RubyTextView);
45
        try {
46
            combinedText = ta.getString(R.styleable.RubyTextView_combinedText);
47
            rubyTextSize = ta.getDimension(R.styleable.RubyTextView_rubyTextSize, 28f);
48
            rubyTextColor = ta.getColor(R.styleable.RubyTextView_rubyTextColor, rubyTextColor);
49
            spacing = ta.getDimension(R.styleable.RubyTextView_spacing, 0);
50
            lineSpacingExtra = ta.getDimension(R.styleable.RubyTextView_lineSpacingExtra, 0);
51
52
        } finally {
53
            ta.recycle();
54
        }
55
56
        setValue();
57
    }
58
59
60
    private void initialize() {
61
        textPaint = getPaint();
62
        rubyTextPaint = new Paint();
63
        originalText = new StringBuilder();
64
        rubyTextColor = getCurrentTextColor();
65
        combinedTextArray = new ArrayList<>();
66
    }
67
68
69
    private void setValue() {
70
        textPaint.setColor(getCurrentTextColor());
71
        rubyTextPaint.setTextSize((getRubyTextSize()));
72
        rubyTextPaint.setColor(getRubyTextColor());
73
        lineheight = getTextSize() + getRubyTextSize() + getLineSpacingExtra() + getSpacing();
74
        firstLineheight = lineheight - getLineSpacingExtra();
75
        splitCombinedText();
76
        setLineHeight((int) lineheight);
77
    }
78
79
    public float getLineSpacingExtra() {
80
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
81
            return super.getLineSpacingExtra();
82
        }
83
        return lineSpacingExtra;
84
    }
85
86
87
    private int getMySize(int measureSpec, int mBoundLength) {
88
        int result;
89
        int specMode = MeasureSpec.getMode(measureSpec);
90
        int specSize = MeasureSpec.getSize(measureSpec);
91
        if (specMode == MeasureSpec.EXACTLY) {
92
            result = specSize;
93
        } else if (specMode == MeasureSpec.AT_MOST) {
94
            result = Math.min(mBoundLength, specSize);
95
        } else {
96
            result = mBoundLength;
97
        }
98
        return result;
99
    }
100
101
102
    @Override
103
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
104
105
        int width = MeasureSpec.getSize(widthMeasureSpec);
106
        float cur_x = 0;
107
        int lineCount = 1;
108
        float maxwidth = 0;
109
110
        for(String[] t : combinedTextArray) {
111
            float textWidth = textPaint.measureText(t[0]);
112
            float rubyWidth = rubyTextPaint.measureText(t[1]);
113
            float elementWidth = Math.max(textWidth, rubyWidth);
114
115
            // if t[0] == '\n'
116
            if(t[0].equals(System.getProperty("line.separator"))){
117
                cur_x = 0;
118
                lineCount++;
119
                continue;
120
            }
121
122
            if (cur_x + elementWidth > width){
123
                cur_x = 0;
124
                lineCount++;
125
            }
126
127
            cur_x += elementWidth;
128
            if(cur_x > maxwidth)
129
                maxwidth = cur_x;
130
        }
131
132
        // total height
133
        int height = getMySize(heightMeasureSpec,
134
                (int) (firstLineheight + lineheight * (lineCount-1)) + getLastBaselineToBottomHeight());
135
        setMeasuredDimension((int) maxwidth, height);
136
    }
137
138
    @Override
139
    protected void onDraw(Canvas canvas) {
140
        boolean isFirstLine = true;
141
        float cur_x = 0;
142
        float cur_y = firstLineheight;
143
        for(String[] t : combinedTextArray) {
144
            /* **********
145
             * Draw text *
146
             * ***********/
147
            float textWidth = textPaint.measureText(t[0]);
148
            float rubyWidth = rubyTextPaint.measureText(t[1]);
149
            float elementWidth = Math.max(textWidth, rubyWidth);
150
151
            if(t[0].equals(System.getProperty("line.separator"))){
152
                cur_x = 0;
153
                if(isFirstLine) isFirstLine = false;
154
                cur_y += lineheight;
155
                continue;
156
            }
157
158
            if (cur_x + textWidth > getWidth()) {
159
                cur_x = 0;
160
                if(isFirstLine) isFirstLine = false;
161
                cur_y += lineheight;
162
            }
163
            float text_posX = cur_x + (1 / 2f) * (elementWidth - textWidth);
164
            canvas.drawText(t[0], text_posX, cur_y, textPaint);
165
166
            /* ****************
167
             * Draw ruby text *
168
             * ****************/
169
            float rubyText_posX = cur_x + (1 / 2f) * (elementWidth - rubyWidth);
170
            canvas.drawText(t[1], rubyText_posX, cur_y - getTextSize() - getSpacing(), rubyTextPaint);
171
172
            // update cur_x position
173
            cur_x += elementWidth;
174
        }
175
    }
176
177
    public String getCombinedText() {
178
        return combinedText;
179
    }
180
    public float getRubyTextSize() {
181
        return rubyTextSize;
182
    }
183
    public float getSpacing() {
184
        return spacing;
185
    }
186
    public int getRubyTextColor() {
187
        return rubyTextColor;
188
    }
189
190
    private void updateLineheight(){
191
        lineheight = getTextSize() + getRubyTextSize() + getLineSpacingExtra() + getSpacing();
192
        firstLineheight = lineheight - getLineSpacingExtra();
193
    }
194
195
    public void setCombinedText(String text) {
196
        combinedText = text;
197
        splitCombinedText();
198
        requestLayout();
199
        invalidate();
200
    }
201
    public void setRubyTextSize(float textSize) {
202
        rubyTextSize = sp2px(textSize);
203
        rubyTextPaint.setTextSize(rubyTextSize);
204
        updateLineheight();
205
        invalidate();
206
        requestLayout();
207
    }
208
    public void setRubyTextColor(int color) {
209
        rubyTextColor = color;
210
        rubyTextPaint.setColor(rubyTextColor);
211
        invalidate();
212
    }
213
214
    @Override
215
    public void setLetterSpacing(float letterSpacing) {
216
        super.setLetterSpacing(letterSpacing);
217
        invalidate();
218
        requestLayout();
219
    }
220
221
    @Override
222
    public void setTextSize(float size) {
223
        super.setTextSize(size);
224
        updateLineheight();
225
        requestLayout();
226
        invalidate();
227
    }
228
229
    public void setSpacing(float spacing) {
230
        this.spacing = dp2px(spacing);
231
        updateLineheight();
232
        invalidate();
233
        requestLayout();
234
    }
235
236
    @Override
237
    public void setTextColor(int color) {
238
        textPaint.setColor(color);
239
        super.setTextColor(color);
240
    }
241
242
    public void splitCombinedText() {
243
        combinedTextArray.clear();
244
        originalText.setLength(0);
245
        if(getCombinedText() == null)
246
            return;
247
248
        String[] split = getCombinedText().split(" ");
249
        for (String value : split) {
250
            String[] t = value.split("\\|");
251
            if (t.length == 2) {
252
                if ((t[1].equals("-"))) {
253
                    combinedTextArray.add(new String[]{t[0], ""});
254
                } else {
255
                    combinedTextArray.add(new String[]{t[0], t[1]});
256
                }
257
            } else {
258
                for (int j = 0; j < t[0].length(); j++) {
259
                    String s = String.valueOf(t[0].charAt(j));
260
                    combinedTextArray.add(new String[]{s, ""});
261
                }
262
            }
263
            originalText.append(t[0]);
264
        }
265
        setText(originalText);
266
    }
267
268
269
    /**
270
     * convert dp to its equivalent px
271
     */
272
    private float dp2px(float dp) {
273
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
274
    }
275
276
    /**
277
     * convert sp to its equivalent px
278
     */
279
    private float sp2px(float sp) {
280
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
281
    }
282
283
}
284