From b5e775e520908af0a5147bc7cae0104ac96ef8d4 Mon Sep 17 00:00:00 2001 From: aNNiMON Date: Tue, 13 Feb 2024 23:53:58 +0200 Subject: [PATCH] Initial commit --- AndroidManifest.xml | 35 ++++ ant.properties | 17 ++ build.xml | 92 +++++++++++ proguard-project.txt | 20 +++ proguard.cfg | 40 +++++ project.properties | 11 ++ res/drawable-hdpi/ic_launcher.png | Bin 0 -> 2321 bytes res/drawable-hdpi/icon_file.png | Bin 0 -> 1384 bytes res/drawable-hdpi/icon_folder.png | Bin 0 -> 2628 bytes res/drawable-ldpi/ic_launcher.png | Bin 0 -> 1048 bytes res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1512 bytes res/layout/file_item.xml | 23 +++ res/layout/main.xml | 15 ++ res/values/strings.xml | 4 + src/com/annimon/text/CP1251Encoding.java | 45 ++++++ src/com/annimon/text/Encoding.java | 52 ++++++ .../annimon/win1251viewer/FileBrowser.java | 151 ++++++++++++++++++ .../win1251viewer/FileBrowserActivity.java | 55 +++++++ .../win1251viewer/FileListAdapter.java | 54 +++++++ .../win1251viewer/FileOpenEventListener.java | 12 ++ .../annimon/win1251viewer/StringEncoder.java | 32 ++++ .../win1251viewer/ViewTextActivity.java | 76 +++++++++ .../win1251viewer/ZoomingTextView.java | 63 ++++++++ 23 files changed, 797 insertions(+) create mode 100644 AndroidManifest.xml create mode 100644 ant.properties create mode 100644 build.xml create mode 100644 proguard-project.txt create mode 100644 proguard.cfg create mode 100644 project.properties create mode 100644 res/drawable-hdpi/ic_launcher.png create mode 100644 res/drawable-hdpi/icon_file.png create mode 100644 res/drawable-hdpi/icon_folder.png create mode 100644 res/drawable-ldpi/ic_launcher.png create mode 100644 res/drawable-mdpi/ic_launcher.png create mode 100644 res/layout/file_item.xml create mode 100644 res/layout/main.xml create mode 100644 res/values/strings.xml create mode 100644 src/com/annimon/text/CP1251Encoding.java create mode 100644 src/com/annimon/text/Encoding.java create mode 100644 src/com/annimon/win1251viewer/FileBrowser.java create mode 100644 src/com/annimon/win1251viewer/FileBrowserActivity.java create mode 100644 src/com/annimon/win1251viewer/FileListAdapter.java create mode 100644 src/com/annimon/win1251viewer/FileOpenEventListener.java create mode 100644 src/com/annimon/win1251viewer/StringEncoder.java create mode 100644 src/com/annimon/win1251viewer/ViewTextActivity.java create mode 100644 src/com/annimon/win1251viewer/ZoomingTextView.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..187cfa8 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ant.properties b/ant.properties new file mode 100644 index 0000000..b0971e8 --- /dev/null +++ b/ant.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..a1be190 --- /dev/null +++ b/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 0000000..b1cdf17 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..895c9ce --- /dev/null +++ b/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-16 diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..49b3e1d2d976b2abdd62dba2f169f498f1ab18a0 GIT binary patch literal 2321 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 literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/icon_folder.png b/res/drawable-hdpi/icon_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..f495b293c1c6e0530335a9b85445be141fab2c10 GIT binary patch literal 2628 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 + + + + + + + + \ No newline at end of file diff --git a/res/layout/main.xml b/res/layout/main.xml new file mode 100644 index 0000000..fd39f80 --- /dev/null +++ b/res/layout/main.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..b8e584e --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Win1251Viewer + diff --git a/src/com/annimon/text/CP1251Encoding.java b/src/com/annimon/text/CP1251Encoding.java new file mode 100644 index 0000000..9bbee46 --- /dev/null +++ b/src/com/annimon/text/CP1251Encoding.java @@ -0,0 +1,45 @@ +/* + * aNNiMON 2012 + * For more info visit http://annimon.com/ + */ +package com.annimon.text; + +/** + * + * @author aNNiMON + */ +public class CP1251Encoding extends Encoding { + + /** Таблица кодировки "windows-1251" */ + protected static char[] cp1251 = { + '\u0410', '\u0411', '\u0412', '\u0413', '\u0414', '\u0415', '\u0416', + '\u0417', '\u0418', '\u0419', '\u041A', '\u041B', '\u041C', '\u041D', + '\u041E', '\u041F', '\u0420', '\u0421', '\u0422', '\u0423', '\u0424', + '\u0425', '\u0426', '\u0427', '\u0428', '\u0429', '\u042A', '\u042B', + '\u042C', '\u042D', '\u042E', '\u042F', '\u0430', '\u0431', '\u0432', + '\u0433', '\u0434', '\u0435', '\u0436', '\u0437', '\u0438', '\u0439', + '\u043A', '\u043B', '\u043C', '\u043D', '\u043E', '\u043F', '\u0440', + '\u0441', '\u0442', '\u0443', '\u0444', '\u0445', '\u0446', '\u0447', + '\u0448', '\u0449', '\u044A', '\u044B', '\u044C', '\u044D', '\u044E', + '\u044F' + }; + + public char decodeChar(byte b) { + int ich = b & 0xff; + if (ich == 0xb8) return 0x0451; // ё + else if (ich == 0xa8) return 0x0401; // Ё + else if (ich >= 0xc0) return cp1251[ich - 192]; + return (char) ich; + } + + public byte encodeChar(char ch) { + if (ch > 0 && ch < 128) return (byte) ch; + else if (ch == 0x401) return -88; // Ё + else if (ch == 0x404) return -86; // Є + else if (ch == 0x407) return -81; // Ї + else if (ch == 0x451) return -72; // ё + else if (ch == 0x454) return -70; // є + else if (ch == 0x457) return -65; // ї + return (byte) (ch + 176); + } +} diff --git a/src/com/annimon/text/Encoding.java b/src/com/annimon/text/Encoding.java new file mode 100644 index 0000000..f51385a --- /dev/null +++ b/src/com/annimon/text/Encoding.java @@ -0,0 +1,52 @@ +/* + * aNNiMON 2012 + * For more info visit http://annimon.com/ + */ +package com.annimon.text; + +import java.io.UnsupportedEncodingException; + +/** + * + * @author aNNiMON + */ +public abstract class Encoding { + + protected String encoding; + + public abstract char decodeChar(byte bt); + + public abstract byte encodeChar(char ch); + + /* + * Кодировать строку s в кодировку enc + */ + public byte[] encodeString(String s, String enc) throws UnsupportedEncodingException { + byte[] bs; + try { + bs = s.getBytes(enc); + } catch (UnsupportedEncodingException x) { + bs = new byte[s.length()]; + for (int i = 0; i < s.length(); i++) { + bs[i] = encodeChar(s.charAt(i)); + } + } + return bs; + } + + /* + * Декодировать участок массива b длиной len со смещения off из кодировки enc + */ + public String decodeString(byte[] bs, int off, int len, String enc) throws UnsupportedEncodingException { + try { + String s = new String(bs, off, len, enc); + return s; + } catch (UnsupportedEncodingException x) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + sb.append( decodeChar(bs[off + i]) ); + } + return sb.toString(); + } + } +} diff --git a/src/com/annimon/win1251viewer/FileBrowser.java b/src/com/annimon/win1251viewer/FileBrowser.java new file mode 100644 index 0000000..1104c08 --- /dev/null +++ b/src/com/annimon/win1251viewer/FileBrowser.java @@ -0,0 +1,151 @@ +package com.annimon.win1251viewer; + +import android.app.ListActivity; +import android.os.Environment; +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * File browser. + * @author aNNiMON + */ +public class FileBrowser { + + // Comparator for sorting files. + private final FilesComparator filesComparator; + // Filter for directories and files. + private final FileFilter dirFilter, fileFilter; + + private String startDir; + private ListActivity activity; + + private FileOpenEventListener fileOpenEventListener; + + private List item, path; + private File currentDir; + + public FileBrowser(ListActivity activity) { + this(activity, Environment.getExternalStorageDirectory().getPath()); + } + + public FileBrowser(ListActivity activity, String startDir) { + this.activity = activity; + this.startDir = startDir; + + item = new ArrayList(); + path = new ArrayList(); + + filesComparator = new FilesComparator(); + dirFilter = new FileFilter() { + // Filter only readable directories. + public boolean accept(File file) { + return (file.isDirectory() && !file.isHidden() && file.canRead()); + } + }; + fileFilter = new FileFilter() { + // Filter only readable files. + public boolean accept(File file) { + return (!file.isDirectory() && !file.isHidden() && file.canRead()); + } + }; + // Begin scan files. + scanDirectory(startDir); + } + + public void setFileOpenEventListener(FileOpenEventListener fileOpenEvent) { + this.fileOpenEventListener = fileOpenEvent; + } + + public String getCurrentDir() { + return currentDir.getAbsolutePath(); + } + + public void setCurrentDir(String path) { + scanDirectory(path); + } + + public void itemSelected(int index) { + File file = new File(path.get(index)); + + if (file.isDirectory()) { + scanDirectory(path.get(index)); + return; + } + + if (fileOpenEventListener != null) { + fileOpenEventListener.onFileOpen(file); + } + } + + public void upDirectory() { + if (currentDir.getPath().equals(startDir)) { + activity.finish(); + } else { + scanDirectory(currentDir.getParent()); + } + } + + public void rescanCurrentDirectory() { + scanDirectory(currentDir.getPath()); + } + + private void scanDirectory(String dirPath) { + activity.setTitle(dirPath); + + item.clear(); + path.clear(); + + File f = new File(dirPath); + currentDir = f; + + if (!dirPath.equals(startDir)) { + item.add("../"); + path.add(f.getParent()); + } + + addDirectories(f); + addFiles(f); + + FileListAdapter fileListAdapter = new FileListAdapter(activity, item); + activity.setListAdapter(fileListAdapter); + } + + private void addDirectories(File file) { + File[] directories = file.listFiles(dirFilter); + sortFiles(directories); + + for (int i = 0; i < directories.length; i++) { + File f = directories[i]; + path.add(f.getPath()); + item.add(f.getName() + "/"); + } + } + + private void addFiles(File file) { + File[] files = file.listFiles(fileFilter); + sortFiles(files); + + for (int i = 0; i < files.length; i++) { + File f = files[i]; + path.add(f.getPath()); + item.add(f.getName()); + } + } + + private void sortFiles(File[] files) { + Arrays.sort(files, filesComparator); + } + + + private class FilesComparator implements Comparator { + + public int compare(File file1, File file2) { + return file1.getName().compareToIgnoreCase(file2.getName()); + } + + } +} diff --git a/src/com/annimon/win1251viewer/FileBrowserActivity.java b/src/com/annimon/win1251viewer/FileBrowserActivity.java new file mode 100644 index 0000000..8115175 --- /dev/null +++ b/src/com/annimon/win1251viewer/FileBrowserActivity.java @@ -0,0 +1,55 @@ +package com.annimon.win1251viewer; + +import android.app.ListActivity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; +import java.io.File; + +/** + * + * @author aNNiMON + */ +public class FileBrowserActivity extends ListActivity implements FileOpenEventListener { + + private FileBrowser fileBrowser; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + fileBrowser = new FileBrowser(this); + fileBrowser.setFileOpenEventListener(this); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putString("dir_path", fileBrowser.getCurrentDir()); + super.onSaveInstanceState(outState); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + String dir = savedInstanceState.getString("dir_path"); + fileBrowser.setCurrentDir(dir); + super.onRestoreInstanceState(savedInstanceState); + } + + @Override + protected void onListItemClick(ListView l, View v, int index, long id) { + fileBrowser.itemSelected(index); + } + + @Override + public void onBackPressed() { + fileBrowser.upDirectory(); + } + + public void onFileOpen(File openedFile) { + Intent intent = new Intent(this, ViewTextActivity.class); + intent.setData(Uri.fromFile(openedFile)); + startActivity(intent); + } +} diff --git a/src/com/annimon/win1251viewer/FileListAdapter.java b/src/com/annimon/win1251viewer/FileListAdapter.java new file mode 100644 index 0000000..93e0311 --- /dev/null +++ b/src/com/annimon/win1251viewer/FileListAdapter.java @@ -0,0 +1,54 @@ +package com.annimon.win1251viewer; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import java.util.List; + +/** + * Adapter for listview [icon filename]. + * @author aNNiMON + */ +public class FileListAdapter extends BaseAdapter { + + private LayoutInflater inflater; + private List objects; + + public FileListAdapter(Context context, List objects) { + this.objects = objects; + this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public int getCount() { + return objects.size(); + } + + public Object getItem(int position) { + return objects.get(position); + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + view = inflater.inflate(R.layout.file_item, parent, false); + } + + String item = (String) getItem(position); + // Set icon by filename type. + int iconId = item.endsWith("/") ? R.drawable.icon_folder : R.drawable.icon_file; + ((ImageView) view.findViewById(R.id.file_item_icon)).setImageResource(iconId); + // Set filename. + ((TextView) view.findViewById(R.id.file_item_name)).setText(item); + + return view; + } + +} diff --git a/src/com/annimon/win1251viewer/FileOpenEventListener.java b/src/com/annimon/win1251viewer/FileOpenEventListener.java new file mode 100644 index 0000000..833cdc2 --- /dev/null +++ b/src/com/annimon/win1251viewer/FileOpenEventListener.java @@ -0,0 +1,12 @@ +package com.annimon.win1251viewer; + +import java.io.File; + +/** + * Listener for event, when user select a file. + * @author aNNiMON + */ +public interface FileOpenEventListener { + + void onFileOpen(File openedFile); +} diff --git a/src/com/annimon/win1251viewer/StringEncoder.java b/src/com/annimon/win1251viewer/StringEncoder.java new file mode 100644 index 0000000..27295f9 --- /dev/null +++ b/src/com/annimon/win1251viewer/StringEncoder.java @@ -0,0 +1,32 @@ +package com.annimon.win1251viewer; + + + +public class StringEncoder +{ + protected static char[] cp1251 = + { + '\u0410', '\u0411', '\u0412', '\u0413', '\u0414', '\u0415', '\u0416', + '\u0417', '\u0418', '\u0419', '\u041A', '\u041B', '\u041C', '\u041D', + '\u041E', '\u041F', '\u0420', '\u0421', '\u0422', '\u0423', '\u0424', + '\u0425', '\u0426', '\u0427', '\u0428', '\u0429', '\u042A', '\u042B', + '\u042C', '\u042D', '\u042E', '\u042F', '\u0430', '\u0431', '\u0432', + '\u0433', '\u0434', '\u0435', '\u0436', '\u0437', '\u0438', '\u0439', + '\u043A', '\u043B', '\u043C', '\u043D', '\u043E', '\u043F', '\u0440', + '\u0441', '\u0442', '\u0443', '\u0444', '\u0445', '\u0446', '\u0447', + '\u0448', '\u0449', '\u044A', '\u044B', '\u044C', '\u044D', '\u044E', + '\u044F' + }; + + public static char decodeCharCP1251 (byte b) + { + int ich = b & 0xff; + if (ich == 0xb8) // ё + return 0x0451; + else if (ich == 0xa8) // Ё + return 0x0401; + else if (ich >= 0xc0) + return cp1251[ich-192]; + return (char)ich; + } +} diff --git a/src/com/annimon/win1251viewer/ViewTextActivity.java b/src/com/annimon/win1251viewer/ViewTextActivity.java new file mode 100644 index 0000000..160e15d --- /dev/null +++ b/src/com/annimon/win1251viewer/ViewTextActivity.java @@ -0,0 +1,76 @@ +package com.annimon.win1251viewer; + +import android.app.*; +import android.net.*; +import android.os.*; +import android.text.method.ScrollingMovementMethod; +import android.view.*; +import java.io.*; + +public class ViewTextActivity extends Activity { + + private ZoomingTextView textView; + private boolean isUTF; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + setContentView(R.layout.main); + + isUTF = false; + String text = getText(isUTF); + textView = (ZoomingTextView) findViewById(R.id.textView); + textView.setMovementMethod(new ScrollingMovementMethod()); + textView.setTextSize(18); + textView.setText(text); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(Menu.NONE, 0, Menu.NONE, "UTF-8"); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + isUTF = !isUTF; + textView.setText(getText(isUTF)); + item.setTitle(!isUTF ? "UTF-8" : "WIN-1251"); + return true; + } + + private String getText(boolean utf) { + String text; + try { + Uri fileUri = getIntent().getData(); + InputStream is = new FileInputStream(fileUri.getPath()); + text = getText(is, utf); + is.close(); + } catch (Exception ex) { + text = "No data"; + } + return text; + } + + private String getText(InputStream is, boolean utf) throws IOException { + if (utf) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int oneByte; + while ((oneByte = is.read()) != -1) { + baos.write((byte) oneByte); + } + baos.flush(); + return new String(baos.toByteArray(), "UTF-8"); + } else { + StringBuilder sb = new StringBuilder(); + + int read; + while ((read = is.read()) != -1) { + sb.append(StringEncoder.decodeCharCP1251((byte) read)); + } + return sb.toString(); + } + } +} diff --git a/src/com/annimon/win1251viewer/ZoomingTextView.java b/src/com/annimon/win1251viewer/ZoomingTextView.java new file mode 100644 index 0000000..5c2f9e7 --- /dev/null +++ b/src/com/annimon/win1251viewer/ZoomingTextView.java @@ -0,0 +1,63 @@ +package com.annimon.win1251viewer; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.widget.TextView; + +/** + * + * @author aNNiMON + */ +public class ZoomingTextView extends TextView { + + private static final int DEFAULT_TEXT_SIZE = 18; + private static final int MINIMAL_TEXT_SIZE = 6, MAXIMAL_TEXT_SIZE = 50; + private static final float MIN_SCALE = MINIMAL_TEXT_SIZE / (float) DEFAULT_TEXT_SIZE; + private static final float MAX_SCALE = MAXIMAL_TEXT_SIZE / (float) DEFAULT_TEXT_SIZE; + + private ScaleGestureDetector scaleDetector; + private float scaleFactor; + + public ZoomingTextView(Context context) { + super(context); + init(context); + } + + public ZoomingTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public ZoomingTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + private void init(Context context) { + scaleDetector = new ScaleGestureDetector(context, new ScaleListener()); + scaleFactor = 1.0f; + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + scaleDetector.onTouchEvent(event); + return super.onTouchEvent(event); + } + + private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + + @Override + public boolean onScale(ScaleGestureDetector detector) { + scaleFactor *= detector.getScaleFactor(); + scaleFactor = Math.max(MIN_SCALE, Math.min(scaleFactor, MAX_SCALE)); + + final int textSize = (int) (scaleFactor * DEFAULT_TEXT_SIZE); + setTextSize(textSize); + + return true; + } + } +}