From 39607ff9a485bdd5e669e9987499aefea893f1b3 Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Wed, 30 Dec 2015 23:41:06 +0100 Subject: [PATCH] DroidFish: Use a specialized View class to speed up drawing of the move list. --- DroidFish/res/layout-land/main.xml | 10 +- DroidFish/res/layout/main.xml | 10 +- DroidFish/res/layout/main_left_handed.xml | 10 +- .../src/org/petero/droidfish/DroidFish.java | 33 ++- .../org/petero/droidfish/MoveListView.java | 191 ++++++++++++++++++ DroidFish/src/org/petero/droidfish/Util.java | 5 +- 6 files changed, 216 insertions(+), 43 deletions(-) create mode 100644 DroidFish/src/org/petero/droidfish/MoveListView.java diff --git a/DroidFish/res/layout-land/main.xml b/DroidFish/res/layout-land/main.xml index 911524e..9ab591d 100644 --- a/DroidFish/res/layout-land/main.xml +++ b/DroidFish/res/layout-land/main.xml @@ -99,17 +99,13 @@ android:layout_above="@+id/scrollViewBot" android:layout_width="fill_parent" android:layout_height="fill_parent"> - + android:padding="0dp"/> diff --git a/DroidFish/res/layout/main.xml b/DroidFish/res/layout/main.xml index 981a6b0..80d0206 100644 --- a/DroidFish/res/layout/main.xml +++ b/DroidFish/res/layout/main.xml @@ -93,17 +93,13 @@ android:layout_above="@+id/scrollViewBot" android:layout_width="fill_parent" android:layout_height="fill_parent"> - + android:padding="0dp"/> diff --git a/DroidFish/res/layout/main_left_handed.xml b/DroidFish/res/layout/main_left_handed.xml index 619e6a7..e76932a 100644 --- a/DroidFish/res/layout/main_left_handed.xml +++ b/DroidFish/res/layout/main_left_handed.xml @@ -94,17 +94,13 @@ android:layout_above="@+id/scrollViewBot" android:layout_width="fill_parent" android:layout_height="fill_parent"> - + android:padding="0dp"/> diff --git a/DroidFish/src/org/petero/droidfish/DroidFish.java b/DroidFish/src/org/petero/droidfish/DroidFish.java index 6a6d5d8..d92f75e 100644 --- a/DroidFish/src/org/petero/droidfish/DroidFish.java +++ b/DroidFish/src/org/petero/droidfish/DroidFish.java @@ -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) { - int currPos = gameTextListener.getCurrPos(); - int line = layout.getLineForOffset(currPos); - int y = (int) ((line - 1.5) * moveList.getLineHeight()); + moveList.setText(gameTextListener.getText()); + int currPos = gameTextListener.getCurrPos(); + 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; diff --git a/DroidFish/src/org/petero/droidfish/MoveListView.java b/DroidFish/src/org/petero/droidfish/MoveListView.java new file mode 100644 index 0000000..ff556bf --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/MoveListView.java @@ -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 . +*/ + +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; + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/Util.java b/DroidFish/src/org/petero/droidfish/Util.java index d4f25c3..6c26f69 100644 --- a/DroidFish/src/org/petero/droidfish/Util.java +++ b/DroidFish/src/org/petero/droidfish/Util.java @@ -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); } } }