DroidFish: Use a specialized View class to speed up drawing of the move

list.
This commit is contained in:
Peter Osterlund 2015-12-30 23:41:06 +01:00
parent 58e10bbc79
commit 39607ff9a4
6 changed files with 216 additions and 43 deletions

View File

@ -99,17 +99,13 @@
android:layout_above="@+id/scrollViewBot"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
<view
class="org.petero.droidfish.MoveListView"
android:id="@+id/moveList"
android:tag="moveList"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:includeFontPadding="true"
android:editable="false"
android:padding="0dp"
android:fontFamily="monospace"
android:typeface="monospace"
android:textSize="12sp"/>
android:padding="0dp"/>
</ScrollView>
</RelativeLayout>
</LinearLayout>

View File

@ -93,17 +93,13 @@
android:layout_above="@+id/scrollViewBot"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
<view
class="org.petero.droidfish.MoveListView"
android:id="@+id/moveList"
android:tag="moveList"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:includeFontPadding="true"
android:editable="false"
android:padding="0dp"
android:fontFamily="monospace"
android:typeface="monospace"
android:textSize="12sp"/>
android:padding="0dp"/>
</ScrollView>
</RelativeLayout>
</LinearLayout>

View File

@ -94,17 +94,13 @@
android:layout_above="@+id/scrollViewBot"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
<view
class="org.petero.droidfish.MoveListView"
android:id="@+id/moveList"
android:tag="moveList"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:includeFontPadding="true"
android:editable="false"
android:padding="0dp"
android:fontFamily="monospace"
android:typeface="monospace"
android:textSize="12sp"/>
android:padding="0dp"/>
</ScrollView>
</RelativeLayout>
</LinearLayout>

View File

@ -104,13 +104,11 @@ import android.preference.PreferenceManager;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.DrawerLayout;
import android.text.Html;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
@ -139,7 +137,6 @@ import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.TextView.BufferType;
import android.widget.Toast;
@SuppressLint("ClickableViewAccessibility")
@ -190,7 +187,7 @@ public class DroidFish extends Activity implements GUIInterface {
private TextView status;
private ScrollView moveListScroll;
private TextView moveList;
private MoveListView moveList;
private TextView thinking;
private ImageButton custom1Button, custom2Button, custom3Button;
private ImageButton modeButton, undoButton, redoButton;
@ -242,7 +239,6 @@ public class DroidFish extends Activity implements GUIInterface {
private boolean useWakeLock = false;
private Typeface figNotation;
private Typeface defaultMoveListTypeFace;
private Typeface defaultThinkingListTypeFace;
@ -667,14 +663,12 @@ public class DroidFish extends Activity implements GUIInterface {
status = (TextView)findViewById(R.id.status);
moveListScroll = (ScrollView)findViewById(R.id.scrollView);
moveList = (TextView)findViewById(R.id.moveList);
defaultMoveListTypeFace = moveList.getTypeface();
moveList = (MoveListView)findViewById(R.id.moveList);
thinking = (TextView)findViewById(R.id.thinking);
defaultThinkingListTypeFace = thinking.getTypeface();
status.setFocusable(false);
moveListScroll.setFocusable(false);
moveList.setFocusable(false);
moveList.setMovementMethod(LinkMovementMethod.getInstance());
thinking.setFocusable(false);
initDrawers();
@ -1029,8 +1023,6 @@ public class DroidFish extends Activity implements GUIInterface {
if (config.orientation == Configuration.ORIENTATION_PORTRAIT)
statusFontSize = Math.min(statusFontSize, 16);
status.setTextSize(statusFontSize);
moveList.setTextSize(fontSize);
thinking.setTextSize(fontSize);
soundEnabled = settings.getBoolean("soundEnabled", false);
vibrateEnabled = settings.getBoolean("vibrateEnabled", false);
animateMoves = settings.getBoolean("animateMoves", true);
@ -1114,13 +1106,13 @@ public class DroidFish extends Activity implements GUIInterface {
if (displayAsFigures) {
// increase the font cause it has different kerning and looks small
float increaseFontSize = fontSize * 1.1f;
moveList.setTypeface(figNotation);
moveList.setTextSize(increaseFontSize);
moveList.setTypeface(figNotation, increaseFontSize);
thinking.setTypeface(figNotation);
thinking.setTextSize(increaseFontSize);
} else {
moveList.setTypeface(defaultMoveListTypeFace);
moveList.setTypeface(null, fontSize);
thinking.setTypeface(defaultThinkingListTypeFace);
thinking.setTextSize(fontSize);
}
}
@ -1717,12 +1709,11 @@ public class DroidFish extends Activity implements GUIInterface {
@Override
public void moveListUpdated() {
moveList.setText(gameTextListener.getSpannableData(), BufferType.SPANNABLE);
Layout layout = moveList.getLayout();
if (layout != null) {
moveList.setText(gameTextListener.getText());
int currPos = gameTextListener.getCurrPos();
int line = layout.getLineForOffset(currPos);
int y = (int) ((line - 1.5) * moveList.getLineHeight());
int line = moveList.getLineForOffset(currPos);
if (line >= 0) {
int y = (line - 1) * moveList.getLineHeight();
moveListScroll.scrollTo(0, y);
}
}
@ -3562,7 +3553,7 @@ public class DroidFish extends Activity implements GUIInterface {
this.options = options;
}
public final SpannableStringBuilder getSpannableData() {
public final CharSequence getText() {
return sb;
}
public final int getCurrPos() {
@ -3722,7 +3713,7 @@ public class DroidFish extends Activity implements GUIInterface {
@Override
public void clear() {
sb.clear();
sb = new SpannableStringBuilder();
prevType = PgnToken.EOF;
nestLevel = 0;
col0 = true;

View File

@ -0,0 +1,191 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2015 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.Spannable;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
/** Custom view for displaying move list.
* This is much faster than using a TextView. */
public class MoveListView extends View {
private CharSequence text = null;
private Layout layout = null;
private int layoutWidth = -1;
private TextPaint textPaint;
private Typeface defaultTypeface;
/** Constructor. */
public MoveListView(Context context, AttributeSet attrs) {
super(context, attrs);
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.density = getResources().getDisplayMetrics().density;
defaultTypeface = Typeface.create("monospace", Typeface.NORMAL);
textPaint.setTypeface(defaultTypeface);
}
/** Set text to display. */
public void setText(CharSequence text) {
if (text != this.text) {
this.text = text;
createLayout(getWidth());
requestLayout();
}
invalidate();
}
/** Set typeface and text size. If tf is null the default typeface is used. */
public void setTypeface(Typeface tf, float size) {
if (tf == null)
tf = defaultTypeface;
boolean modified = false;
if (tf != textPaint.getTypeface()) {
textPaint.setTypeface(tf);
modified = true;
}
DisplayMetrics metric = getContext().getResources().getDisplayMetrics();
size *= metric.scaledDensity;
if (size != textPaint.getTextSize()) {
textPaint.setTextSize(size);
modified = true;
}
if (modified) {
createLayout(getWidth());
requestLayout();
invalidate();
}
}
public void setTextColor(int color) {
if (color != textPaint.getColor()) {
textPaint.setColor(color);
invalidate();
}
}
/** Get line number corresponding to a character offset,
* or -1 if layout has not been created yet. */
public int getLineForOffset(int currPos) {
if (layout == null)
return -1;
return layout.getLineForOffset(currPos);
}
/** Get line height in pixels. */
public int getLineHeight() {
return textPaint.getFontMetricsInt(null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMeasure = MeasureSpec.getSize(widthMeasureSpec);
int heightMeasure = MeasureSpec.getSize(heightMeasureSpec);
int width = getMeasuredWidth();
switch (MeasureSpec.getMode(widthMeasureSpec)) {
case MeasureSpec.UNSPECIFIED:
break;
case MeasureSpec.EXACTLY:
width = widthMeasure;
break;
case MeasureSpec.AT_MOST:
width = Math.min(width, widthMeasure);
break;
}
if (width != layoutWidth)
createLayout(width);
int height = 0;
if (layout != null)
height = layout.getLineCount() * getLineHeight();
switch (MeasureSpec.getMode(heightMeasureSpec)) {
case MeasureSpec.UNSPECIFIED:
break;
case MeasureSpec.EXACTLY:
height = heightMeasure;
break;
case MeasureSpec.AT_MOST:
height = Math.min(height, heightMeasure);
break;
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (layout != null) {
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());
layout.draw(canvas);
canvas.restore();
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
boolean ret = super.onTouchEvent(event);
if ((action == MotionEvent.ACTION_UP) && (layout != null) &&
(text instanceof Spannable)) {
Spannable spannable = (Spannable)text;
int x = (int)event.getX() - getPaddingLeft() + getScrollX();
int y = (int)event.getY() - getPaddingTop() + getScrollY();
int line = layout.getLineForVertical(y);
int offs = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = spannable.getSpans(offs, offs, ClickableSpan.class);
if (link.length > 0) {
link[0].onClick(this);
return true;
}
}
return ret;
}
/** Create a StaticLayout corresponding to the current text. */
private void createLayout(int width) {
if (width <= 0)
return;
if (text == null) {
layout = null;
layoutWidth = -1;
} else {
layout = new StaticLayout(text, textPaint, width,
Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
layoutWidth = width;
}
}
}

View File

@ -121,7 +121,7 @@ public final class Util {
final int bg = ColorTheme.instance().getColor(ColorTheme.GENERAL_BACKGROUND);
Object tag = v.getTag();
final boolean excludedItems = v instanceof Button ||
((v instanceof EditText) && !"moveList".equals(tag)) ||
((v instanceof EditText) && !(v instanceof MoveListView)) ||
v instanceof ImageButton ||
"title".equals(tag);
if (!excludedItems) {
@ -140,6 +140,9 @@ public final class Util {
} else if (!excludedItems && (v instanceof TextView)) {
int fg = ColorTheme.instance().getColor(ColorTheme.FONT_FOREGROUND);
((TextView) v).setTextColor(fg);
} else if (!excludedItems && (v instanceof MoveListView)) {
int fg = ColorTheme.instance().getColor(ColorTheme.FONT_FOREGROUND);
((MoveListView) v).setTextColor(fg);
}
}
}