Move custom views to their own package

Julien LepillerSun Jun 26 19:24:55+0200 2022

35b961c

Move custom views to their own package

app/src/main/java/eu/lepiller/nani/PitchContourView.java unknown status 2

1-
package eu.lepiller.nani;
2-
3-
import android.content.Context;
4-
import android.graphics.Canvas;
5-
import android.graphics.Paint;
6-
import android.util.AttributeSet;
7-
import android.util.Pair;
8-
9-
import androidx.annotation.Nullable;
10-
11-
public class PitchContourView extends PitchView {
12-
    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
13-
    private final static int spacing = 4;
14-
15-
    public PitchContourView(Context context) {
16-
        super(context);
17-
    }
18-
19-
    public PitchContourView(Context context, @Nullable AttributeSet attrs) {
20-
        super(context, attrs);
21-
    }
22-
23-
    public PitchContourView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
24-
        super(context, attrs, defStyleAttr);
25-
    }
26-
27-
    @Override
28-
    protected void onDraw(Canvas canvas) {
29-
        super.onDraw(canvas);
30-
        if(pitchedMora == null || pitchedMora.size() == 0)
31-
            return;
32-
33-
        paint.setColor(textColor);
34-
        paint.setTextSize(textSize);
35-
        paint.setStrokeWidth(2);
36-
37-
        int x = 0;
38-
        for(int i = 0; i< pitchedMora.size(); i++) {
39-
            Pair<String, Boolean> mora = pitchedMora.get(i);
40-
            float posX = getX(x, textSize);
41-
            String text = mora.first == null? "???": mora.first;
42-
            x += text.length();
43-
            float width = paint.measureText(text);
44-
45-
            paint.setStyle(Paint.Style.FILL);
46-
            paint.setTextSize((float)textSize);
47-
            paint.setStrokeWidth(textSize);
48-
            canvas.drawText(text, posX+spacing, textSize + textSize / 4, paint);
49-
50-
            paint.setStrokeWidth(2);
51-
            paint.setStyle(Paint.Style.STROKE);
52-
            if(mora.second) {
53-
                canvas.drawLine(posX, 0, posX+width+2*spacing, 0, paint);
54-
            } else {
55-
                canvas.drawLine(posX, textSize + textSize/2, posX+width+2*spacing, textSize + textSize/2, paint);
56-
            }
57-
58-
            if(i+1<pitchedMora.size()) {
59-
                Pair<String, Boolean> mora2 = pitchedMora.get(i+1);
60-
                if(mora2.second != mora.second) {
61-
                    canvas.drawLine(posX+width+2*spacing, 0, posX+width+2*spacing, textSize + textSize/2, paint);
62-
                }
63-
            }
64-
        }
65-
66-
    }
67-
68-
    private static float getX(int i, float textSize) {
69-
        return i*textSize + i*2*spacing + textSize/2;
70-
    }
71-
72-
    private int totalSize() {
73-
        int total = 0;
74-
        for(int i = 0; i< pitchedMora.size(); i++) {
75-
            Pair<String, Boolean> mora = pitchedMora.get(i);
76-
            if(mora.first != null)
77-
                total += mora.first.length();
78-
        }
79-
        return total+1;
80-
    }
81-
82-
    @Override
83-
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
84-
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
85-
86-
        if(pitchedMora != null)
87-
            setMeasuredDimension((int)getX(totalSize(), textSize), (int)(textSize + textSize/2));
88-
        else
89-
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
90-
    }
91-
}

app/src/main/java/eu/lepiller/nani/PitchDiagramView.java unknown status 2

1-
package eu.lepiller.nani;
2-
3-
import android.content.Context;
4-
import android.graphics.Canvas;
5-
import android.graphics.Paint;
6-
import android.util.AttributeSet;
7-
import android.util.Pair;
8-
9-
import androidx.annotation.Nullable;
10-
11-
public class PitchDiagramView extends PitchView {
12-
    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
13-
14-
    public PitchDiagramView(Context context) {
15-
        super(context);
16-
    }
17-
18-
    public PitchDiagramView(Context context, @Nullable AttributeSet attrs) {
19-
        super(context, attrs);
20-
    }
21-
22-
    public PitchDiagramView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
23-
        super(context, attrs, defStyleAttr);
24-
    }
25-
26-
    private static float getCharWidth(float textSize) {
27-
        return (float)2.25 * textSize;
28-
    }
29-
30-
    private static float getPitchRadius(float textSize) {
31-
        return textSize / 3;
32-
    }
33-
34-
    private static float getStrokeWidth(float textSize) {
35-
        return getPitchRadius(textSize) / 2;
36-
    }
37-
38-
    private static float getParticleRadius(float textSize) {
39-
        return 2 * getPitchRadius(textSize) / 3;
40-
    }
41-
42-
    private static float getLowPosition(float textSize) {
43-
        return 2 * textSize + getParticleRadius(textSize);
44-
    }
45-
46-
    private static float getHighPosition(float textSize) {
47-
        return getPitchRadius(textSize) + getParticleRadius(textSize);
48-
    }
49-
50-
    private static float getMoraPosition(float textSize) {
51-
        return (float)3.5 * textSize + getParticleRadius(textSize);
52-
    }
53-
54-
    @Override
55-
    protected void onDraw(Canvas canvas) {
56-
        super.onDraw(canvas);
57-
        if(pitchedMora == null || pitchedMora.size() == 0)
58-
            return;
59-
60-
        paint.setStrokeWidth(getParticleRadius(textSize));
61-
        paint.setTextSize(textSize);
62-
        boolean previous = true;
63-
64-
        for(int i = 0; i< pitchedMora.size(); i++) {
65-
            paint.setColor(pitchColor);
66-
            Pair<String, Boolean> mora = pitchedMora.get(i);
67-
            paint.setStyle(mora.first==null? Paint.Style.STROKE: Paint.Style.FILL_AND_STROKE);
68-
            float x = getX(i, textSize);
69-
            float y = getY(mora.second, textSize);
70-
            canvas.drawCircle(x, y, getPitchRadius(textSize), paint);
71-
72-
            if(i>0) {
73-
                paint.setStyle(Paint.Style.STROKE);
74-
                paint.setStrokeWidth(getStrokeWidth(textSize));
75-
                double radius = getPitchRadius(textSize) + getParticleRadius(textSize)/2;
76-
                drawLine(canvas, paint, i-1, previous, mora.second, radius, textSize);
77-
                paint.setStrokeWidth(getParticleRadius(textSize));
78-
            }
79-
80-
            if(mora.first != null) {
81-
                paint.setColor(textColor);
82-
                float width = paint.measureText(mora.first);
83-
                paint.setStyle(Paint.Style.FILL);
84-
                canvas.drawText(mora.first, x - width/2, getMoraPosition(textSize), paint);
85-
            }
86-
87-
            previous = mora.second;
88-
        }
89-
    }
90-
91-
    private static void drawLine(Canvas canvas, Paint paint, int position, boolean a, boolean b, double radius, float textSize) {
92-
        float startX = getX(position, textSize);
93-
        float startY = getY(a, textSize);
94-
        float endX = getX(position + 1, textSize);
95-
        float endY = getY(b, textSize);
96-
97-
        if(a == b) {
98-
            startX += radius;
99-
            endX -= radius;
100-
        } else {
101-
            double dist = Math.sqrt(Math.pow(startX-endX, 2)+Math.pow(startY-endY, 2));
102-
            startY += (endY - startY) * radius / dist;
103-
            startX += (endX - startX) * radius / dist;
104-
105-
            endX -= (endX - startX) * radius / dist;
106-
            endY -= (endY - startY) * radius / dist;
107-
        }
108-
109-
        canvas.drawLine(startX, startY, endX, endY, paint);
110-
    }
111-
112-
    private static float getX(int i, float textSize) {
113-
        return i*getCharWidth(textSize) + getCharWidth(textSize)/2;
114-
    }
115-
116-
    private static float getY(boolean high, float textSize) {
117-
        return high? getHighPosition(textSize): getLowPosition(textSize);
118-
    }
119-
120-
    @Override
121-
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
122-
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
123-
124-
        if(pitchedMora != null)
125-
            setMeasuredDimension((int)getX(pitchedMora.size(), textSize), (int)(getMoraPosition(textSize) + textSize/2));
126-
        else
127-
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
128-
    }
129-
}

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

1-
package eu.lepiller.nani;
2-
3-
import android.content.Context;
4-
import android.content.res.Configuration;
5-
import android.content.res.TypedArray;
6-
import android.graphics.Color;
7-
import android.graphics.Paint;
8-
import android.util.AttributeSet;
9-
import android.util.Pair;
10-
import android.view.View;
11-
import android.widget.TextView;
12-
13-
import androidx.annotation.Nullable;
14-
15-
import java.util.ArrayList;
16-
17-
public abstract class PitchView extends View {
18-
    private int accent;
19-
    private String text;
20-
21-
    protected int textColor = Color.BLACK;
22-
    protected int pitchColor = Color.BLACK;
23-
    protected ArrayList<Pair<String, Boolean>> pitchedMora = new ArrayList<>();
24-
    protected float textSize = 32;
25-
26-
    public PitchView(Context context) {
27-
        super(context);
28-
        setDefaults();
29-
    }
30-
31-
    public PitchView(Context context, @Nullable AttributeSet attrs) {
32-
        super(context, attrs);
33-
        setDefaults();
34-
        setupAttributes(attrs, 0);
35-
    }
36-
37-
    public PitchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
38-
        super(context, attrs, defStyleAttr);
39-
        setDefaults();
40-
        setupAttributes(attrs, defStyleAttr);
41-
    }
42-
43-
    private void setDefaults() {
44-
        textColor = getContext().getResources().getColor(android.R.color.tab_indicator_text);
45-
        int mode = getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
46-
        if(mode == Configuration.UI_MODE_NIGHT_NO) {
47-
            pitchColor = getContext().getResources().getColor(android.R.color.background_dark);
48-
        } else if(mode == Configuration.UI_MODE_NIGHT_YES) {
49-
            pitchColor = getContext().getResources().getColor(android.R.color.background_light);
50-
        }
51-
        textSize = new TextView(getContext()).getTextSize();
52-
    }
53-
54-
    private void setupAttributes(AttributeSet attrs, int defStyleAttr) {
55-
        TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.PitchDiagramView, defStyleAttr, 0);
56-
        textColor = typedArray.getColor(R.styleable.PitchDiagramView_textColor, textColor);
57-
        pitchColor = typedArray.getColor(R.styleable.PitchDiagramView_pitchColor, pitchColor);
58-
        textSize = typedArray.getDimension(R.styleable.PitchDiagramView_textSize, textSize);
59-
        text = typedArray.getString(R.styleable.PitchDiagramView_text);
60-
        accent = typedArray.getInt(R.styleable.PitchDiagramView_pitch, -1);
61-
        updatePitchedMora();
62-
    }
63-
64-
    protected static boolean is_mora(char c) {
65-
        ArrayList<Character> not_mora = new ArrayList<>();
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-
        not_mora.add("???".charAt(0));
72-
        return !not_mora.contains(c);
73-
    }
74-
75-
    public void setText(String text) {
76-
        this.text = text;
77-
        updatePitchedMora();
78-
    }
79-
80-
    public void setPitch(int pitch) {
81-
        this.accent = pitch;
82-
        updatePitchedMora();
83-
    }
84-
85-
    public String getText() {
86-
        return text;
87-
    }
88-
89-
    public int getPitch() {
90-
        return accent;
91-
    }
92-
93-
    private void updatePitchedMora() {
94-
        ArrayList<String> moras = new ArrayList<>();
95-
96-
        if(accent < 0 || text == null)
97-
            return;
98-
99-
        for(int i=0; i<text.length(); i++) {
100-
            char here = text.charAt(i);
101-
            if(is_mora(here) && (i == text.length()-1 || is_mora(text.charAt(i+1))))
102-
                moras.add(text.substring(i,i+1));
103-
            else if(is_mora(here))
104-
                moras.add(text.substring(i,i+2));
105-
        }
106-
        moras.add(null);
107-
        pitchedMora = new ArrayList<>();
108-
        if(accent == 0)
109-
            setHeiban(pitchedMora, moras);
110-
        else if(accent == 1)
111-
            setAtamadaka(pitchedMora, moras);
112-
        else
113-
            setOtherAccent(pitchedMora, moras, accent);
114-
    }
115-
116-
    public void setTextColor(int textColor) {
117-
        this.textColor = textColor;
118-
    }
119-
120-
    public void setPitchColor(int pitchColor) {
121-
        this.pitchColor = pitchColor;
122-
    }
123-
124-
    public void setTextSize(float textSize) {
125-
        this.textSize = textSize;
126-
    }
127-
128-
    private static void setHeiban(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras) {
129-
        text.add(new Pair<>(moras.get(0), false));
130-
        for(int i=1; i<moras.size(); i++) {
131-
            text.add(new Pair<>(moras.get(i), true));
132-
        }
133-
    }
134-
135-
    private static void setAtamadaka(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras) {
136-
        text.add(new Pair<>(moras.get(0), true));
137-
        for(int i=1; i<moras.size(); i++) {
138-
            text.add(new Pair<>(moras.get(i), false));
139-
        }
140-
    }
141-
142-
    private static void setOtherAccent(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras, int accent) {
143-
        text.add(new Pair<>(moras.get(0), false));
144-
        int i;
145-
        for(i=1; i<moras.size() && i < accent; i++) {
146-
            text.add(new Pair<>(moras.get(i), true));
147-
        }
148-
        for(; i<moras.size(); i++) {
149-
            text.add(new Pair<>(moras.get(i), false));
150-
        }
151-
    }
152-
}

app/src/main/java/eu/lepiller/nani/ResultPagerAdapter.java

1919
import java.util.List;
2020
import java.util.Map;
2121
22+
import eu.lepiller.nani.views.PitchContourView;
23+
import eu.lepiller.nani.views.PitchDiagramView;
2224
import me.weilunli.views.RubyTextView;
2325
import eu.lepiller.nani.result.KanjiResult;
2426
import eu.lepiller.nani.result.Result;

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

1+
package eu.lepiller.nani.views;
2+
3+
import android.content.Context;
4+
import android.graphics.Canvas;
5+
import android.graphics.Paint;
6+
import android.util.AttributeSet;
7+
import android.util.Pair;
8+
9+
import androidx.annotation.Nullable;
10+
11+
public class PitchContourView extends PitchView {
12+
    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
13+
    private final static int spacing = 4;
14+
15+
    public PitchContourView(Context context) {
16+
        super(context);
17+
    }
18+
19+
    public PitchContourView(Context context, @Nullable AttributeSet attrs) {
20+
        super(context, attrs);
21+
    }
22+
23+
    public PitchContourView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
24+
        super(context, attrs, defStyleAttr);
25+
    }
26+
27+
    @Override
28+
    protected void onDraw(Canvas canvas) {
29+
        super.onDraw(canvas);
30+
        if(pitchedMora == null || pitchedMora.size() == 0)
31+
            return;
32+
33+
        paint.setColor(textColor);
34+
        paint.setTextSize(textSize);
35+
        paint.setStrokeWidth(2);
36+
37+
        int x = 0;
38+
        for(int i = 0; i< pitchedMora.size(); i++) {
39+
            Pair<String, Boolean> mora = pitchedMora.get(i);
40+
            float posX = getX(x, textSize);
41+
            String text = mora.first == null? "???": mora.first;
42+
            x += text.length();
43+
            float width = paint.measureText(text);
44+
45+
            paint.setStyle(Paint.Style.FILL);
46+
            paint.setTextSize((float)textSize);
47+
            paint.setStrokeWidth(textSize);
48+
            canvas.drawText(text, posX+spacing, textSize + textSize / 4, paint);
49+
50+
            paint.setStrokeWidth(2);
51+
            paint.setStyle(Paint.Style.STROKE);
52+
            if(mora.second) {
53+
                canvas.drawLine(posX, 0, posX+width+2*spacing, 0, paint);
54+
            } else {
55+
                canvas.drawLine(posX, textSize + textSize/2, posX+width+2*spacing, textSize + textSize/2, paint);
56+
            }
57+
58+
            if(i+1<pitchedMora.size()) {
59+
                Pair<String, Boolean> mora2 = pitchedMora.get(i+1);
60+
                if(mora2.second != mora.second) {
61+
                    canvas.drawLine(posX+width+2*spacing, 0, posX+width+2*spacing, textSize + textSize/2, paint);
62+
                }
63+
            }
64+
        }
65+
66+
    }
67+
68+
    private static float getX(int i, float textSize) {
69+
        return i*textSize + i*2*spacing + textSize/2;
70+
    }
71+
72+
    private int totalSize() {
73+
        int total = 0;
74+
        for(int i = 0; i< pitchedMora.size(); i++) {
75+
            Pair<String, Boolean> mora = pitchedMora.get(i);
76+
            if(mora.first != null)
77+
                total += mora.first.length();
78+
        }
79+
        return total+1;
80+
    }
81+
82+
    @Override
83+
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
84+
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
85+
86+
        if(pitchedMora != null)
87+
            setMeasuredDimension((int)getX(totalSize(), textSize), (int)(textSize + textSize/2));
88+
        else
89+
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
90+
    }
91+
}

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

1+
package eu.lepiller.nani.views;
2+
3+
import android.content.Context;
4+
import android.graphics.Canvas;
5+
import android.graphics.Paint;
6+
import android.util.AttributeSet;
7+
import android.util.Pair;
8+
9+
import androidx.annotation.Nullable;
10+
11+
public class PitchDiagramView extends PitchView {
12+
    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
13+
14+
    public PitchDiagramView(Context context) {
15+
        super(context);
16+
    }
17+
18+
    public PitchDiagramView(Context context, @Nullable AttributeSet attrs) {
19+
        super(context, attrs);
20+
    }
21+
22+
    public PitchDiagramView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
23+
        super(context, attrs, defStyleAttr);
24+
    }
25+
26+
    private static float getCharWidth(float textSize) {
27+
        return (float)2.25 * textSize;
28+
    }
29+
30+
    private static float getPitchRadius(float textSize) {
31+
        return textSize / 3;
32+
    }
33+
34+
    private static float getStrokeWidth(float textSize) {
35+
        return getPitchRadius(textSize) / 2;
36+
    }
37+
38+
    private static float getParticleRadius(float textSize) {
39+
        return 2 * getPitchRadius(textSize) / 3;
40+
    }
41+
42+
    private static float getLowPosition(float textSize) {
43+
        return 2 * textSize + getParticleRadius(textSize);
44+
    }
45+
46+
    private static float getHighPosition(float textSize) {
47+
        return getPitchRadius(textSize) + getParticleRadius(textSize);
48+
    }
49+
50+
    private static float getMoraPosition(float textSize) {
51+
        return (float)3.5 * textSize + getParticleRadius(textSize);
52+
    }
53+
54+
    @Override
55+
    protected void onDraw(Canvas canvas) {
56+
        super.onDraw(canvas);
57+
        if(pitchedMora == null || pitchedMora.size() == 0)
58+
            return;
59+
60+
        paint.setStrokeWidth(getParticleRadius(textSize));
61+
        paint.setTextSize(textSize);
62+
        boolean previous = true;
63+
64+
        for(int i = 0; i< pitchedMora.size(); i++) {
65+
            paint.setColor(pitchColor);
66+
            Pair<String, Boolean> mora = pitchedMora.get(i);
67+
            paint.setStyle(mora.first==null? Paint.Style.STROKE: Paint.Style.FILL_AND_STROKE);
68+
            float x = getX(i, textSize);
69+
            float y = getY(mora.second, textSize);
70+
            canvas.drawCircle(x, y, getPitchRadius(textSize), paint);
71+
72+
            if(i>0) {
73+
                paint.setStyle(Paint.Style.STROKE);
74+
                paint.setStrokeWidth(getStrokeWidth(textSize));
75+
                double radius = getPitchRadius(textSize) + getParticleRadius(textSize)/2;
76+
                drawLine(canvas, paint, i-1, previous, mora.second, radius, textSize);
77+
                paint.setStrokeWidth(getParticleRadius(textSize));
78+
            }
79+
80+
            if(mora.first != null) {
81+
                paint.setColor(textColor);
82+
                float width = paint.measureText(mora.first);
83+
                paint.setStyle(Paint.Style.FILL);
84+
                canvas.drawText(mora.first, x - width/2, getMoraPosition(textSize), paint);
85+
            }
86+
87+
            previous = mora.second;
88+
        }
89+
    }
90+
91+
    private static void drawLine(Canvas canvas, Paint paint, int position, boolean a, boolean b, double radius, float textSize) {
92+
        float startX = getX(position, textSize);
93+
        float startY = getY(a, textSize);
94+
        float endX = getX(position + 1, textSize);
95+
        float endY = getY(b, textSize);
96+
97+
        if(a == b) {
98+
            startX += radius;
99+
            endX -= radius;
100+
        } else {
101+
            double dist = Math.sqrt(Math.pow(startX-endX, 2)+Math.pow(startY-endY, 2));
102+
            startY += (endY - startY) * radius / dist;
103+
            startX += (endX - startX) * radius / dist;
104+
105+
            endX -= (endX - startX) * radius / dist;
106+
            endY -= (endY - startY) * radius / dist;
107+
        }
108+
109+
        canvas.drawLine(startX, startY, endX, endY, paint);
110+
    }
111+
112+
    private static float getX(int i, float textSize) {
113+
        return i*getCharWidth(textSize) + getCharWidth(textSize)/2;
114+
    }
115+
116+
    private static float getY(boolean high, float textSize) {
117+
        return high? getHighPosition(textSize): getLowPosition(textSize);
118+
    }
119+
120+
    @Override
121+
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
122+
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
123+
124+
        if(pitchedMora != null)
125+
            setMeasuredDimension((int)getX(pitchedMora.size(), textSize), (int)(getMoraPosition(textSize) + textSize/2));
126+
        else
127+
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
128+
    }
129+
}

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

1+
package eu.lepiller.nani.views;
2+
3+
import android.content.Context;
4+
import android.content.res.Configuration;
5+
import android.content.res.TypedArray;
6+
import android.graphics.Color;
7+
import android.util.AttributeSet;
8+
import android.util.Pair;
9+
import android.view.View;
10+
import android.widget.TextView;
11+
12+
import androidx.annotation.Nullable;
13+
14+
import java.util.ArrayList;
15+
16+
import eu.lepiller.nani.R;
17+
18+
public abstract class PitchView extends View {
19+
    private int accent;
20+
    private String text;
21+
22+
    protected int textColor = Color.BLACK;
23+
    protected int pitchColor = Color.BLACK;
24+
    protected ArrayList<Pair<String, Boolean>> pitchedMora = new ArrayList<>();
25+
    protected float textSize = 32;
26+
27+
    public PitchView(Context context) {
28+
        super(context);
29+
        setDefaults();
30+
    }
31+
32+
    public PitchView(Context context, @Nullable AttributeSet attrs) {
33+
        super(context, attrs);
34+
        setDefaults();
35+
        setupAttributes(attrs, 0);
36+
    }
37+
38+
    public PitchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
39+
        super(context, attrs, defStyleAttr);
40+
        setDefaults();
41+
        setupAttributes(attrs, defStyleAttr);
42+
    }
43+
44+
    private void setDefaults() {
45+
        textColor = getContext().getResources().getColor(android.R.color.tab_indicator_text);
46+
        int mode = getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
47+
        if(mode == Configuration.UI_MODE_NIGHT_NO) {
48+
            pitchColor = getContext().getResources().getColor(android.R.color.background_dark);
49+
        } else if(mode == Configuration.UI_MODE_NIGHT_YES) {
50+
            pitchColor = getContext().getResources().getColor(android.R.color.background_light);
51+
        }
52+
        textSize = new TextView(getContext()).getTextSize();
53+
    }
54+
55+
    private void setupAttributes(AttributeSet attrs, int defStyleAttr) {
56+
        TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.PitchDiagramView, defStyleAttr, 0);
57+
        textColor = typedArray.getColor(R.styleable.PitchDiagramView_textColor, textColor);
58+
        pitchColor = typedArray.getColor(R.styleable.PitchDiagramView_pitchColor, pitchColor);
59+
        textSize = typedArray.getDimension(R.styleable.PitchDiagramView_textSize, textSize);
60+
        text = typedArray.getString(R.styleable.PitchDiagramView_text);
61+
        accent = typedArray.getInt(R.styleable.PitchDiagramView_pitch, -1);
62+
        updatePitchedMora();
63+
    }
64+
65+
    protected static boolean is_mora(char c) {
66+
        ArrayList<Character> not_mora = new ArrayList<>();
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+
        not_mora.add("???".charAt(0));
72+
        not_mora.add("???".charAt(0));
73+
        return !not_mora.contains(c);
74+
    }
75+
76+
    public void setText(String text) {
77+
        this.text = text;
78+
        updatePitchedMora();
79+
    }
80+
81+
    public void setPitch(int pitch) {
82+
        this.accent = pitch;
83+
        updatePitchedMora();
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+
    }
128+
129+
    private static void setHeiban(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras) {
130+
        text.add(new Pair<>(moras.get(0), false));
131+
        for(int i=1; i<moras.size(); i++) {
132+
            text.add(new Pair<>(moras.get(i), true));
133+
        }
134+
    }
135+
136+
    private static void setAtamadaka(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras) {
137+
        text.add(new Pair<>(moras.get(0), true));
138+
        for(int i=1; i<moras.size(); i++) {
139+
            text.add(new Pair<>(moras.get(i), false));
140+
        }
141+
    }
142+
143+
    private static void setOtherAccent(ArrayList<Pair<String, Boolean>> text, ArrayList<String> moras, int accent) {
144+
        text.add(new Pair<>(moras.get(0), false));
145+
        int i;
146+
        for(i=1; i<moras.size() && i < accent; i++) {
147+
            text.add(new Pair<>(moras.get(i), true));
148+
        }
149+
        for(; i<moras.size(); i++) {
150+
            text.add(new Pair<>(moras.get(i), false));
151+
        }
152+
    }
153+
}

app/src/main/res/layout/activity_help_pitch.xml

126126
                        android:layout_height="wrap_content"
127127
                        android:background="@color/colorWhite">
128128
129-
                        <eu.lepiller.nani.PitchDiagramView
129+
                        <eu.lepiller.nani.views.PitchDiagramView
130130
                            android:layout_width="wrap_content"
131131
                            android:layout_height="wrap_content"
132132
                            android:layout_gravity="center"

150150
                        android:layout_height="wrap_content"
151151
                        android:background="@color/colorGrey">
152152
153-
                        <eu.lepiller.nani.PitchDiagramView
153+
                        <eu.lepiller.nani.views.PitchDiagramView
154154
                            android:layout_width="wrap_content"
155155
                            android:layout_height="wrap_content"
156156
                            android:layout_gravity="center"

182182
                        android:layout_height="wrap_content"
183183
                        android:background="@color/colorWhite">
184184
185-
                        <eu.lepiller.nani.PitchDiagramView
185+
                        <eu.lepiller.nani.views.PitchDiagramView
186186
                            android:layout_width="wrap_content"
187187
                            android:layout_height="wrap_content"
188188
                            android:layout_gravity="center"

206206
                        android:layout_height="wrap_content"
207207
                        android:background="@color/colorGrey">
208208
209-
                        <eu.lepiller.nani.PitchDiagramView
209+
                        <eu.lepiller.nani.views.PitchDiagramView
210210
                            android:layout_width="wrap_content"
211211
                            android:layout_height="wrap_content"
212212
                            android:layout_gravity="center"

230230
                        android:layout_height="wrap_content"
231231
                        android:background="@color/colorDarkerGrey">
232232
233-
                        <eu.lepiller.nani.PitchDiagramView
233+
                        <eu.lepiller.nani.views.PitchDiagramView
234234
                            android:layout_width="wrap_content"
235235
                            android:layout_height="wrap_content"
236236
                            android:layout_gravity="center"

260260
                        android:layout_height="wrap_content"
261261
                        android:background="@color/colorWhite">
262262
263-
                        <eu.lepiller.nani.PitchDiagramView
263+
                        <eu.lepiller.nani.views.PitchDiagramView
264264
                            android:layout_width="wrap_content"
265265
                            android:layout_height="wrap_content"
266266
                            android:layout_gravity="center"

284284
                        android:layout_height="wrap_content"
285285
                        android:background="@color/colorGrey">
286286
287-
                        <eu.lepiller.nani.PitchDiagramView
287+
                        <eu.lepiller.nani.views.PitchDiagramView
288288
                            android:layout_width="wrap_content"
289289
                            android:layout_height="wrap_content"
290290
                            android:layout_gravity="center"

308308
                        android:layout_height="wrap_content"
309309
                        android:background="@color/colorDarkGrey">
310310
311-
                        <eu.lepiller.nani.PitchDiagramView
311+
                        <eu.lepiller.nani.views.PitchDiagramView
312312
                            android:layout_width="wrap_content"
313313
                            android:layout_height="wrap_content"
314314
                            android:layout_gravity="center"

332332
                        android:layout_height="wrap_content"
333333
                        android:background="@color/colorDarkerGrey">
334334
335-
                        <eu.lepiller.nani.PitchDiagramView
335+
                        <eu.lepiller.nani.views.PitchDiagramView
336336
                            android:layout_width="wrap_content"
337337
                            android:layout_height="wrap_content"
338338
                            android:layout_gravity="center"

362362
                        android:background="@color/colorWhite"
363363
                        android:orientation="vertical">
364364
365-
                        <eu.lepiller.nani.PitchDiagramView
365+
                        <eu.lepiller.nani.views.PitchDiagramView
366366
                            android:layout_width="wrap_content"
367367
                            android:layout_height="wrap_content"
368368
                            android:layout_gravity="center"

387387
                        android:layout_height="wrap_content"
388388
                        android:background="@color/colorGrey">
389389
390-
                        <eu.lepiller.nani.PitchDiagramView
390+
                        <eu.lepiller.nani.views.PitchDiagramView
391391
                            android:layout_width="wrap_content"
392392
                            android:layout_height="wrap_content"
393393
                            android:layout_gravity="center"

411411
                        android:layout_height="wrap_content"
412412
                        android:background="@color/colorDarkGrey">
413413
414-
                        <eu.lepiller.nani.PitchDiagramView
414+
                        <eu.lepiller.nani.views.PitchDiagramView
415415
                            android:layout_width="wrap_content"
416416
                            android:layout_height="wrap_content"
417417
                            android:layout_gravity="center"

435435
                        android:layout_height="wrap_content"
436436
                        android:background="@color/colorDarkGrey">
437437
438-
                        <eu.lepiller.nani.PitchDiagramView
438+
                        <eu.lepiller.nani.views.PitchDiagramView
439439
                            android:layout_width="wrap_content"
440440
                            android:layout_height="wrap_content"
441441
                            android:layout_gravity="center"

459459
                        android:layout_height="wrap_content"
460460
                        android:background="@color/colorDarkerGrey">
461461
462-
                        <eu.lepiller.nani.PitchDiagramView
462+
                        <eu.lepiller.nani.views.PitchDiagramView
463463
                            android:layout_width="wrap_content"
464464
                            android:layout_height="wrap_content"
465465
                            android:layout_gravity="center"

app/src/main/res/layout/layout_result.xml

5252
                android:minWidth="@dimen/title_size"
5353
                android:visibility="gone" />
5454
55-
            <eu.lepiller.nani.PitchContourView
55+
            <eu.lepiller.nani.views.PitchContourView
5656
                android:id="@+id/pitch_contour_view"
5757
                android:layout_width="wrap_content"
5858
                android:layout_height="wrap_content"

6969
                android:minWidth="@dimen/title_size"
7070
                android:visibility="gone" />
7171
72-
            <eu.lepiller.nani.PitchDiagramView
72+
            <eu.lepiller.nani.views.PitchDiagramView
7373
                android:id="@+id/pitch_diagram_view"
7474
                android:layout_width="wrap_content"
7575
                android:layout_height="wrap_content"