mirror of
https://github.com/gedoor/legado.git
synced 2024-09-01 09:34:25 +08:00
优化
This commit is contained in:
parent
c73c692aa2
commit
6b5c15ee66
@ -203,7 +203,7 @@ dependencies {
|
||||
implementation("org.nanohttpd:nanohttpd-websocket:$nanoHttpdVersion")
|
||||
|
||||
//代码编辑
|
||||
implementation('com.github.AmrDeveloper:CodeView:1.0.1')
|
||||
//implementation('com.github.AmrDeveloper:CodeView:1.0.1')
|
||||
|
||||
//二维码
|
||||
implementation('com.github.jenly1314:zxing-lite:2.1.1')
|
||||
|
449
app/src/main/java/io/legado/app/ui/widget/code/CodeView.java
Normal file
449
app/src/main/java/io/legado/app/ui/widget/code/CodeView.java
Normal file
@ -0,0 +1,449 @@
|
||||
package io.legado.app.ui.widget.code;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class CodeView extends AppCompatMultiAutoCompleteTextView {
|
||||
|
||||
private int tabWidth;
|
||||
private int tabWidthInCharacters;
|
||||
private int mUpdateDelayTime = 500;
|
||||
|
||||
private boolean modified = true;
|
||||
private boolean highlightWhileTextChanging = true;
|
||||
|
||||
private boolean hasErrors = false;
|
||||
private boolean mRemoveErrorsWhenTextChanged = true;
|
||||
|
||||
private final Handler mUpdateHandler = new Handler();
|
||||
private Tokenizer mAutoCompleteTokenizer;
|
||||
private final float displayDensity = getResources().getDisplayMetrics().density;
|
||||
|
||||
private static final Pattern PATTERN_LINE = Pattern.compile("(^.+$)+", Pattern.MULTILINE);
|
||||
private static final Pattern PATTERN_TRAILING_WHITE_SPACE = Pattern.compile("[\\t ]+$", Pattern.MULTILINE);
|
||||
|
||||
private final SortedMap<Integer, Integer> mErrorHashSet = new TreeMap<>();
|
||||
private final Map<Pattern, Integer> mSyntaxPatternMap = new HashMap<>();
|
||||
private List<Character> mIndentCharacterList = Arrays.asList('{', '+', '-', '*', '/', '=');
|
||||
|
||||
public CodeView(Context context) {
|
||||
super(context);
|
||||
initEditorView();
|
||||
}
|
||||
|
||||
public CodeView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initEditorView();
|
||||
}
|
||||
|
||||
public CodeView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initEditorView();
|
||||
}
|
||||
|
||||
private void initEditorView() {
|
||||
if (mAutoCompleteTokenizer == null) {
|
||||
mAutoCompleteTokenizer = new KeywordTokenizer();
|
||||
}
|
||||
|
||||
setTokenizer(mAutoCompleteTokenizer);
|
||||
|
||||
setFilters(new InputFilter[]{
|
||||
new InputFilter() {
|
||||
@Override
|
||||
public CharSequence filter(CharSequence source, int start, int end,
|
||||
Spanned dest, int dstart, int dend) {
|
||||
if (modified &&
|
||||
end - start == 1 &&
|
||||
start < source.length() &&
|
||||
dstart < dest.length()) {
|
||||
char c = source.charAt(start);
|
||||
|
||||
if (c == '\n') {
|
||||
return autoIndent(source, dest, dstart, dend);
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
}
|
||||
});
|
||||
addTextChangedListener(mEditorTextWatcher);
|
||||
}
|
||||
|
||||
private CharSequence autoIndent(CharSequence source, Spanned dest, int dstart, int dend) {
|
||||
String indent = "";
|
||||
int istart = dstart - 1;
|
||||
|
||||
boolean dataBefore = false;
|
||||
int pt = 0;
|
||||
|
||||
for (; istart > -1; --istart) {
|
||||
char c = dest.charAt(istart);
|
||||
|
||||
if (c == '\n') break;
|
||||
|
||||
if (c != ' ' && c != '\t') {
|
||||
if (!dataBefore) {
|
||||
if (mIndentCharacterList.contains(c)) --pt;
|
||||
dataBefore = true;
|
||||
}
|
||||
|
||||
if (c == '(') {
|
||||
--pt;
|
||||
} else if (c == ')') {
|
||||
++pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (istart > -1) {
|
||||
char charAtCursor = dest.charAt(dstart);
|
||||
int iend;
|
||||
|
||||
for (iend = ++istart; iend < dend; ++iend) {
|
||||
char c = dest.charAt(iend);
|
||||
|
||||
if (charAtCursor != '\n' &&
|
||||
c == '/' &&
|
||||
iend + 1 < dend &&
|
||||
dest.charAt(iend) == c) {
|
||||
iend += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c != ' ' && c != '\t') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
indent += dest.subSequence(istart, iend);
|
||||
}
|
||||
|
||||
if (pt < 0) {
|
||||
indent += "\t";
|
||||
}
|
||||
|
||||
return source + indent;
|
||||
}
|
||||
|
||||
private void highlightSyntax(Editable editable) {
|
||||
if (mSyntaxPatternMap.isEmpty()) return;
|
||||
|
||||
for (Pattern pattern : mSyntaxPatternMap.keySet()) {
|
||||
int color = mSyntaxPatternMap.get(pattern);
|
||||
for (Matcher m = pattern.matcher(editable); m.find(); ) {
|
||||
createForegroundColorSpan(editable, m, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void highlightErrorLines(Editable editable) {
|
||||
if (mErrorHashSet.isEmpty()) return;
|
||||
int maxErrorLineValue = mErrorHashSet.lastKey();
|
||||
|
||||
int lineNumber = 0;
|
||||
Matcher matcher = PATTERN_LINE.matcher(editable);
|
||||
while (matcher.find()) {
|
||||
if (mErrorHashSet.containsKey(lineNumber)) {
|
||||
int color = mErrorHashSet.get(lineNumber);
|
||||
createBackgroundColorSpan(editable, matcher, color);
|
||||
}
|
||||
lineNumber = lineNumber + 1;
|
||||
if (lineNumber > maxErrorLineValue) break;
|
||||
}
|
||||
}
|
||||
|
||||
private void createForegroundColorSpan(Editable editable, Matcher matcher, @ColorInt int color) {
|
||||
editable.setSpan(new ForegroundColorSpan(color),
|
||||
matcher.start(), matcher.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
private void createBackgroundColorSpan(Editable editable, Matcher matcher, @ColorInt int color) {
|
||||
editable.setSpan(new BackgroundColorSpan(color),
|
||||
matcher.start(), matcher.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
private Editable highlight(Editable editable) {
|
||||
if (editable.length() == 0) return editable;
|
||||
|
||||
try {
|
||||
clearSpans(editable);
|
||||
highlightErrorLines(editable);
|
||||
highlightSyntax(editable);
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return editable;
|
||||
}
|
||||
|
||||
private void highlightWithoutChange(Editable editable) {
|
||||
modified = false;
|
||||
highlight(editable);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
public void setTextHighlighted(CharSequence text) {
|
||||
if (text == null || text.length() == 0) return;
|
||||
|
||||
cancelHighlighterRender();
|
||||
|
||||
removeAllErrorLines();
|
||||
|
||||
modified = false;
|
||||
setText(highlight(new SpannableStringBuilder(text)));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
public void setTabWidth(int characters) {
|
||||
if (tabWidthInCharacters == characters) return;
|
||||
tabWidthInCharacters = characters;
|
||||
tabWidth = Math.round(getPaint().measureText("m") * characters);
|
||||
}
|
||||
|
||||
private void clearSpans(Editable editable) {
|
||||
int length = editable.length();
|
||||
ForegroundColorSpan[] foregroundSpans = editable.getSpans(
|
||||
0, length, ForegroundColorSpan.class);
|
||||
|
||||
for (int i = foregroundSpans.length; i-- > 0; )
|
||||
editable.removeSpan(foregroundSpans[i]);
|
||||
|
||||
BackgroundColorSpan[] backgroundSpans = editable.getSpans(
|
||||
0, length, BackgroundColorSpan.class);
|
||||
|
||||
for (int i = backgroundSpans.length; i-- > 0; )
|
||||
editable.removeSpan(backgroundSpans[i]);
|
||||
}
|
||||
|
||||
public void cancelHighlighterRender() {
|
||||
mUpdateHandler.removeCallbacks(mUpdateRunnable);
|
||||
}
|
||||
|
||||
private void convertTabs(Editable editable, int start, int count) {
|
||||
if (tabWidth < 1) return;
|
||||
|
||||
String s = editable.toString();
|
||||
|
||||
for (int stop = start + count;
|
||||
(start = s.indexOf("\t", start)) > -1 && start < stop;
|
||||
++start) {
|
||||
editable.setSpan(new TabWidthSpan(),
|
||||
start,
|
||||
start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSyntaxPatternsMap(Map<Pattern, Integer> syntaxPatterns) {
|
||||
if (!mSyntaxPatternMap.isEmpty()) mSyntaxPatternMap.clear();
|
||||
mSyntaxPatternMap.putAll(syntaxPatterns);
|
||||
}
|
||||
|
||||
public void addSyntaxPattern(Pattern pattern, @ColorInt int Color) {
|
||||
mSyntaxPatternMap.put(pattern, Color);
|
||||
}
|
||||
|
||||
public void removeSyntaxPattern(Pattern pattern) {
|
||||
mSyntaxPatternMap.remove(pattern);
|
||||
}
|
||||
|
||||
public int getSyntaxPatternsSize() {
|
||||
return mSyntaxPatternMap.size();
|
||||
}
|
||||
|
||||
public void resetSyntaxPatternList() {
|
||||
mSyntaxPatternMap.clear();
|
||||
}
|
||||
|
||||
public void setAutoIndentCharacterList(List<Character> characterList) {
|
||||
mIndentCharacterList = characterList;
|
||||
}
|
||||
|
||||
public void clearAutoIndentCharacterList() {
|
||||
mIndentCharacterList.clear();
|
||||
}
|
||||
|
||||
public List<Character> getAutoIndentCharacterList() {
|
||||
return mIndentCharacterList;
|
||||
}
|
||||
|
||||
public void addErrorLine(int lineNum, int color) {
|
||||
mErrorHashSet.put(lineNum, color);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
public void removeErrorLine(int lineNum) {
|
||||
mErrorHashSet.remove(lineNum);
|
||||
hasErrors = mErrorHashSet.size() > 0;
|
||||
}
|
||||
|
||||
public void removeAllErrorLines() {
|
||||
mErrorHashSet.clear();
|
||||
hasErrors = false;
|
||||
}
|
||||
|
||||
public int getErrorsSize() {
|
||||
return mErrorHashSet.size();
|
||||
}
|
||||
|
||||
public String getTextWithoutTrailingSpace() {
|
||||
return PATTERN_TRAILING_WHITE_SPACE
|
||||
.matcher(getText())
|
||||
.replaceAll("");
|
||||
}
|
||||
|
||||
public void setAutoCompleteTokenizer(Tokenizer tokenizer) {
|
||||
mAutoCompleteTokenizer = tokenizer;
|
||||
}
|
||||
|
||||
public void setRemoveErrorsWhenTextChanged(boolean removeErrors) {
|
||||
mRemoveErrorsWhenTextChanged = removeErrors;
|
||||
}
|
||||
|
||||
public void reHighlightSyntax() {
|
||||
highlightSyntax(getEditableText());
|
||||
}
|
||||
|
||||
public void reHighlightErrors() {
|
||||
highlightErrorLines(getEditableText());
|
||||
}
|
||||
|
||||
public boolean isHasError() {
|
||||
return hasErrors;
|
||||
}
|
||||
|
||||
public void setUpdateDelayTime(int time) {
|
||||
mUpdateDelayTime = time;
|
||||
}
|
||||
|
||||
public int getUpdateDelayTime() {
|
||||
return mUpdateDelayTime;
|
||||
}
|
||||
|
||||
public void setHighlightWhileTextChanging(boolean updateWhileTextChanging) {
|
||||
this.highlightWhileTextChanging = updateWhileTextChanging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDropDown() {
|
||||
int[] screenPoint = new int[2];
|
||||
getLocationOnScreen(screenPoint);
|
||||
|
||||
final Rect displayFrame = new Rect();
|
||||
getWindowVisibleDisplayFrame(displayFrame);
|
||||
|
||||
int position = getSelectionStart();
|
||||
Layout layout = getLayout();
|
||||
int line = layout.getLineForOffset(position);
|
||||
|
||||
float verticalDistanceInDp = (750 + 140 * line) / displayDensity;
|
||||
setDropDownVerticalOffset((int) verticalDistanceInDp);
|
||||
|
||||
float horizontalDistanceInDp = layout.getPrimaryHorizontal(position) / displayDensity;
|
||||
setDropDownHorizontalOffset((int) horizontalDistanceInDp);
|
||||
super.showDropDown();
|
||||
}
|
||||
|
||||
private final Runnable mUpdateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Editable source = getText();
|
||||
highlightWithoutChange(source);
|
||||
}
|
||||
};
|
||||
|
||||
private final TextWatcher mEditorTextWatcher = new TextWatcher() {
|
||||
|
||||
private int start;
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||
this.start = start;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||
if (!modified) return;
|
||||
|
||||
if (highlightWhileTextChanging) {
|
||||
if (mSyntaxPatternMap.size() > 0) {
|
||||
convertTabs(getEditableText(), start, count);
|
||||
mUpdateHandler.postDelayed(mUpdateRunnable, mUpdateDelayTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (mRemoveErrorsWhenTextChanged) removeAllErrorLines();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
if (!highlightWhileTextChanging) {
|
||||
if (!modified) return;
|
||||
|
||||
cancelHighlighterRender();
|
||||
|
||||
if (mSyntaxPatternMap.size() > 0) {
|
||||
convertTabs(getEditableText(), start, count);
|
||||
mUpdateHandler.postDelayed(mUpdateRunnable, mUpdateDelayTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final class TabWidthSpan extends ReplacementSpan {
|
||||
|
||||
@Override
|
||||
public int getSize(
|
||||
@NonNull Paint paint,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
Paint.FontMetricsInt fm) {
|
||||
return tabWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(
|
||||
@NonNull Canvas canvas,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
float x,
|
||||
int top,
|
||||
int y,
|
||||
int bottom,
|
||||
@NonNull Paint paint) {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.legado.app.ui.widget.code;
|
||||
|
||||
import android.widget.MultiAutoCompleteTextView;
|
||||
|
||||
public class KeywordTokenizer implements MultiAutoCompleteTextView.Tokenizer {
|
||||
|
||||
@Override
|
||||
public int findTokenStart(CharSequence charSequence, int cursor) {
|
||||
String sequenceStr = charSequence.toString();
|
||||
sequenceStr = sequenceStr.substring(0, cursor);
|
||||
|
||||
int spaceIndex = sequenceStr.lastIndexOf(" ");
|
||||
int lineIndex = sequenceStr.lastIndexOf("\n");
|
||||
int bracketIndex = sequenceStr.lastIndexOf("(");
|
||||
|
||||
int index = Math.max(0, Math.max(spaceIndex, Math.max(lineIndex, bracketIndex)));
|
||||
if (index == 0) return 0;
|
||||
return (index + 1 < charSequence.length()) ? index + 1 : index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findTokenEnd(CharSequence charSequence, int cursor) {
|
||||
return charSequence.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence terminateToken(CharSequence charSequence) {
|
||||
return charSequence;
|
||||
}
|
||||
|
||||
}
|
@ -2,8 +2,9 @@
|
||||
|
||||
package io.legado.app.utils
|
||||
|
||||
import com.amrdeveloper.codeview.CodeView
|
||||
|
||||
import io.legado.app.lib.theme.accentColor
|
||||
import io.legado.app.ui.widget.code.CodeView
|
||||
import splitties.init.appCtx
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background">
|
||||
|
||||
<com.amrdeveloper.codeview.CodeView
|
||||
<io.legado.app.ui.widget.code.CodeView
|
||||
android:id="@+id/code_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -5,9 +5,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="3dp">
|
||||
|
||||
<com.amrdeveloper.codeview.CodeView
|
||||
<io.legado.app.ui.widget.code.CodeView
|
||||
android:id="@+id/editText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine" />
|
||||
|
||||
</io.legado.app.ui.widget.text.TextInputLayout>
|
Loading…
Reference in New Issue
Block a user