Add a pitch view widget

Julien LepillerThu Jun 11 04:21:54+0200 2020

9ca7f50

Add a pitch view widget

app/src/main/java/eu/lepiller/nani/PitchView.java unknown status 1

1+
package eu.lepiller.nani;
2+
3+
import android.content.Context;
4+
import android.content.res.TypedArray;
5+
import android.graphics.Canvas;
6+
import android.graphics.Color;
7+
import android.graphics.Paint;
8+
import android.util.AttributeSet;
9+
import android.util.Log;
10+
import android.util.Pair;
11+
import android.view.View;
12+
import android.widget.TextView;
13+
14+
import androidx.annotation.Nullable;
15+
16+
import java.util.ArrayList;
17+
18+
public class PitchView extends View {
19+
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
20+
    private int textColor = Color.BLACK;
21+
    private int pitchColor = Color.BLACK;
22+
    private ArrayList<Pair<String, Boolean>> pitchedMora = new ArrayList<>();
23+
    private int accent;
24+
    private String text;
25+
26+
    private float textSize = 32;
27+
28+
    private static final String TAG = "PITCHVIEW";
29+
30+
    public PitchView(Context context) {
31+
        super(context);
32+
        setDefaults();
33+
    }
34+
35+
    public PitchView(Context context, @Nullable AttributeSet attrs) {
36+
        super(context, attrs);
37+
        setDefaults();
38+
        setupAttributes(attrs, 0);
39+
    }
40+
41+
    public PitchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
42+
        super(context, attrs, defStyleAttr);
43+
        setDefaults();
44+
        setupAttributes(attrs, defStyleAttr);
45+
    }
46+
47+
    private void setDefaults() {
48+
        textColor = getContext().getResources().getColor(android.R.color.tab_indicator_text);
49+
        pitchColor = getContext().getResources().getColor(android.R.color.background_dark);
50+
        textSize = new TextView(getContext()).getTextSize();
51+
    }
52+
53+
    private void setupAttributes(AttributeSet attrs, int defStyleAttr) {
54+
        TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.PitchView, defStyleAttr, 0);
55+
        textColor = typedArray.getColor(R.styleable.PitchView_textColor, textColor);
56+
        pitchColor = typedArray.getColor(R.styleable.PitchView_pitchColor, pitchColor);
57+
        textSize = typedArray.getDimension(R.styleable.PitchView_textSize, textSize);
58+
        text = typedArray.getString(R.styleable.PitchView_text);
59+
        accent = typedArray.getInt(R.styleable.PitchView_pitch, -1);
60+
        updatePitchedMora();
61+
    }
62+
63+
    private static boolean is_mora(char c) {
64+
        ArrayList<Character> not_mora = new ArrayList<>();
65+
        not_mora.add("???".charAt(0));
66+
        not_mora.add("???".charAt(0));
67+
        not_mora.add("???".charAt(0));
68+
        not_mora.add("???".charAt(0));
69+
        not_mora.add("???".charAt(0));
70+
        not_mora.add("???".charAt(0));
71+
        return !not_mora.contains(c);
72+
    }
73+
74+
    public void setText(String text) {
75+
        this.text = text;
76+
        updatePitchedMora();
77+
        notify();
78+
    }
79+
80+
    public void setPitch(int pitch) {
81+
        this.accent = pitch;
82+
        updatePitchedMora();
83+
        notify();
84+
    }
85+
86+
    public String getText() {
87+
        return text;
88+
    }
89+
90+
    public int getPitch() {
91+
        return accent;
92+
    }
93+
94+
    private void updatePitchedMora() {
95+
        ArrayList<String> moras = new ArrayList<>();
96+
97+
        if(accent < 0 || text == null)
98+
            return;
99+
100+
        for(int i=0; i<text.length(); i++) {
101+
            char here = text.charAt(i);
102+
            if(is_mora(here) && (i == text.length()-1 || is_mora(text.charAt(i+1))))
103+
                moras.add(text.substring(i,i+1));
104+
            else if(is_mora(here))
105+
                moras.add(text.substring(i,i+2));
106+
        }
107+
        moras.add(null);
108+
        pitchedMora = new ArrayList<>();
109+
        if(accent == 0)
110+
            setHeiban(pitchedMora, moras);
111+
        else if(accent == 1)
112+
            setAtamadaka(pitchedMora, moras);
113+
        else
114+
            setOtherAccent(pitchedMora, moras, accent);
115+
    }
116+
117+
    public void setTextColor(int textColor) {
118+
        this.textColor = textColor;
119+
    }
120+
121+
    public void setPitchColor(int pitchColor) {
122+
        this.pitchColor = pitchColor;
123+
    }
124+
125+
    public void setTextSize(float textSize) {
126+
        this.textSize = textSize;
127+
        notify();
128+
    }
129+
130+
    private static float getCharWidth(float textSize) {
131+
        return (float)2.25 * textSize;
132+
    }
133+
134+
    private static float getPitchRadius(float textSize) {
135+
        return textSize / 3;
136+
    }
137+
138+
    private static float getStrokeWidth(float textSize) {
139+
        return getPitchRadius(textSize) / 2;
140+
    }
141+
142+
    private static float getParticleRadius(float textSize) {
143+
        return 2 * getPitchRadius(textSize) / 3;
144+
    }
145+
146+
    private static float getLowPosition(float textSize) {
147+
        return 2 * textSize + getParticleRadius(textSize);
148+
    }
149+
150+
    private static float getHighPosition(float textSize) {
151+
        return getPitchRadius(textSize) + getParticleRadius(textSize);
152+
    }
153+
154+
    private static float getMoraPosition(float textSize) {
155+
        return (float)3.5 * textSize + getParticleRadius(textSize);
156+
    }
157+
158+
    private static void setHeiban(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras) {
159+
        text.add(new Pair<>(moras.get(0), false));
160+
        for(int i=1; i<moras.size(); i++) {
161+
            text.add(new Pair<>(moras.get(i), true));
162+
        }
163+
    }
164+
165+
    private static void setAtamadaka(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras) {
166+
        text.add(new Pair<>(moras.get(0), true));
167+
        for(int i=1; i<moras.size(); i++) {
168+
            text.add(new Pair<>(moras.get(i), false));
169+
        }
170+
    }
171+
172+
    private static void setOtherAccent(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras, int accent) {
173+
        text.add(new Pair<>(moras.get(0), false));
174+
        int i;
175+
        for(i=1; i<moras.size() && i < accent; i++) {
176+
            text.add(new Pair<>(moras.get(i), true));
177+
        }
178+
        for(; i<moras.size(); i++) {
179+
            text.add(new Pair<>(moras.get(i), false));
180+
        }
181+
    }
182+
183+
    @Override
184+
    protected void onDraw(Canvas canvas) {
185+
        super.onDraw(canvas);
186+
        if(pitchedMora == null || pitchedMora.size() == 0)
187+
            return;
188+
189+
        paint.setStrokeWidth(getParticleRadius(textSize));
190+
        paint.setTextSize(textSize);
191+
        boolean previous = true;
192+
193+
        for(int i = 0; i< pitchedMora.size(); i++) {
194+
            paint.setColor(pitchColor);
195+
            Pair<String, Boolean> mora = pitchedMora.get(i);
196+
            paint.setStyle(mora.first==null? Paint.Style.STROKE: Paint.Style.FILL_AND_STROKE);
197+
            float x = getX(i, textSize);
198+
            float y = getY(mora.second, textSize);
199+
            canvas.drawCircle(x, y, getPitchRadius(textSize), paint);
200+
201+
            if(i>0) {
202+
                paint.setStyle(Paint.Style.STROKE);
203+
                paint.setStrokeWidth(getStrokeWidth(textSize));
204+
                double radius = getPitchRadius(textSize) + getParticleRadius(textSize)/2;
205+
                drawLine(canvas, paint, i-1, previous, mora.second, radius, textSize);
206+
                paint.setStrokeWidth(getParticleRadius(textSize));
207+
            }
208+
209+
            if(mora.first != null) {
210+
                paint.setColor(textColor);
211+
                float width = paint.measureText(mora.first);
212+
                paint.setStyle(Paint.Style.FILL);
213+
                canvas.drawText(mora.first, x - width/2, getMoraPosition(textSize), paint);
214+
            }
215+
216+
            previous = mora.second;
217+
        }
218+
    }
219+
220+
    private static void drawLine(Canvas canvas, Paint paint, int position, boolean a, boolean b, double radius, float textSize) {
221+
        float startX = getX(position, textSize);
222+
        float startY = getY(a, textSize);
223+
        float endX = getX(position + 1, textSize);
224+
        float endY = getY(b, textSize);
225+
226+
        if(a == b) {
227+
            startX += radius;
228+
            endX -= radius;
229+
        } else {
230+
            double dist = Math.sqrt(Math.pow(startX-endX, 2)+Math.pow(startY-endY, 2));
231+
            startY += (endY - startY) * radius / dist;
232+
            startX += (endX - startX) * radius / dist;
233+
234+
            endX -= (endX - startX) * radius / dist;
235+
            endY -= (endY - startY) * radius / dist;
236+
        }
237+
238+
        canvas.drawLine(startX, startY, endX, endY, paint);
239+
    }
240+
241+
    private static float getX(int i, float textSize) {
242+
        return i*getCharWidth(textSize) + getCharWidth(textSize)/2;
243+
    }
244+
245+
    private static float getY(boolean high, float textSize) {
246+
        return high? getHighPosition(textSize): getLowPosition(textSize);
247+
    }
248+
249+
    @Override
250+
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
251+
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
252+
253+
        if(pitchedMora != null)
254+
           setMeasuredDimension((int)getX(pitchedMora.size(), textSize), (int)(getMoraPosition(textSize) + textSize/2));
255+
        else
256+
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
257+
    }
258+
}

app/src/main/res/values/attrs.xml unknown status 1

1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources></resources>
2<
03<
\ No newline at end of file