Pertanyaan Mendiskusikan teks di Android


Saya menulis penampil teks / eBook sederhana untuk Android, jadi saya telah menggunakan a TextView untuk menampilkan teks berformat HTML ke pengguna, sehingga mereka dapat menelusuri teks di halaman dengan pergi dan maju. Tetapi masalah saya adalah bahwa saya tidak dapat meng-posting teks di Android.

Saya tidak bisa (atau saya tidak tahu caranya) mendapatkan umpan balik yang sesuai dari algoritma pemecah baris dan pemecah halaman di mana TextView digunakan untuk memecah teks menjadi garis dan halaman. Dengan demikian, saya tidak dapat mengerti di mana konten berakhir di layar yang sebenarnya, sehingga saya melanjutkan dari yang tersisa di halaman berikutnya. Saya ingin menemukan cara untuk mengatasi masalah ini.

Jika saya tahu apa karakter terakhir yang dilukis pada layar, saya dapat dengan mudah memasukkan cukup karakter untuk mengisi layar, dan mengetahui di mana lukisan yang sebenarnya selesai, saya dapat melanjutkan di halaman berikutnya. Apakah ini mungkin? Bagaimana?


Pertanyaan serupa telah ditanyakan beberapa kali pada StackOverflow, tetapi tidak ada jawaban memuaskan yang diberikan. Ini hanya beberapa dari mereka:

Ada satu jawaban, yang tampaknya berhasil, tetapi lambat. Ini menambah karakter dan garis sampai halaman diisi. Saya rasa ini bukan cara yang baik untuk melakukan pematahan halaman:

Daripada pertanyaan ini, itu terjadi PageTurner pembaca e-book apakah itu sebagian besar benar, meskipun entah bagaimana lambat.

screenshot of PageTurner eBook reader


PS: Saya tidak terbatas pada TextView, dan saya tahu line breaking dan algoritma page breaking bisa sangat kompleks (seperti di TeX), jadi saya tidak mencari jawaban yang optimal, melainkan solusi yang cukup cepat yang dapat digunakan oleh pengguna.


Memperbarui: Ini tampaknya menjadi awal yang baik untuk mendapatkan jawaban yang benar:

Apakah ada cara untuk mengambil jumlah atau rentang garis Tampilan TextView?

Menjawab: Setelah menyelesaikan tata letak teks, dimungkinkan untuk mengetahui teks yang terlihat:

ViewTreeObserver vto = txtViewEx.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                ViewTreeObserver obs = txtViewEx.getViewTreeObserver();
                obs.removeOnGlobalLayoutListener(this);
                height = txtViewEx.getHeight();
                scrollY = txtViewEx.getScrollY();
                Layout layout = txtViewEx.getLayout();

                firstVisibleLineNumber = layout.getLineForVertical(scrollY);
                lastVisibleLineNumber = layout.getLineForVertical(height+scrollY);

            }
        });

32
2017-08-05 16:19


asal


Jawaban:


Latar Belakang

Apa yang kami ketahui tentang pemrosesan teks di dalamnya TextView adalah bahwa ini melanggar teks dengan garis sesuai dengan lebar tampilan. Melihat ke TextView's sumber kita dapat melihat bahwa pemrosesan teks dilakukan oleh Layout kelas. Jadi kita bisa memanfaatkan karya tersebut Layout kelas untuk kita dan menggunakan metode yang dilakukan pagination.

Masalah

Masalah dengan TextView adalah bahwa bagian teks yang terlihat mungkin dipotong secara vertikal di suatu tempat di tengah garis yang terlihat terakhir. Berkenaan dengan itu, kita harus memecah halaman baru ketika baris terakhir yang sepenuhnya cocok dengan tinggi tampilan terpenuhi.

Algoritma

  • Kami mengulangi melalui baris teks dan memeriksa apakah garis itu bottom melebihi tinggi tampilan;
  • Jika demikian, kami memecah halaman baru dan menghitung nilai baru untuk tinggi kumulatif untuk membandingkan baris berikut ' bottomdenganlihat implementasinya). Nilai baru didefinisikan sebagai top nilai (garis merah pada gambar di bawah ini) dari garis yang belum sesuai dengan halaman sebelumnya + TextView's tinggi.

enter image description here

Pelaksanaan

public class Pagination {
    private final boolean mIncludePad;
    private final int mWidth;
    private final int mHeight;
    private final float mSpacingMult;
    private final float mSpacingAdd;
    private final CharSequence mText;
    private final TextPaint mPaint;
    private final List<CharSequence> mPages;

    public Pagination(CharSequence text, int pageW, int pageH, TextPaint paint, float spacingMult, float spacingAdd, boolean inclidePad) {
        this.mText = text;
        this.mWidth = pageW;
        this.mHeight = pageH;
        this.mPaint = paint;
        this.mSpacingMult = spacingMult;
        this.mSpacingAdd = spacingAdd;
        this.mIncludePad = inclidePad;
        this.mPages = new ArrayList<CharSequence>();

        layout();
    }

    private void layout() {
        final StaticLayout layout = new StaticLayout(mText, mPaint, mWidth, Layout.Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, mIncludePad);

        final int lines = layout.getLineCount();
        final CharSequence text = layout.getText();
        int startOffset = 0;
        int height = mHeight;

        for (int i = 0; i < lines; i++) {
            if (height < layout.getLineBottom(i)) {
                // When the layout height has been exceeded
                addPage(text.subSequence(startOffset, layout.getLineStart(i)));
                startOffset = layout.getLineStart(i);
                height = layout.getLineTop(i) + mHeight;
            }

            if (i == lines - 1) {
                // Put the rest of the text into the last page
                addPage(text.subSequence(startOffset, layout.getLineEnd(i)));
                return;
            }
        }
    }

    private void addPage(CharSequence text) {
        mPages.add(text);
    }

    public int size() {
        return mPages.size();
    }

    public CharSequence get(int index) {
        return (index >= 0 && index < mPages.size()) ? mPages.get(index) : null;
    }
}

Catatan 1

Algoritme berfungsi tidak hanya untuk TextView (Pagination penggunaan kelas TextView's parameter dalam implementasi di atas). Anda dapat melewati serangkaian parameter apa pun StaticLayout menerima dan kemudian menggunakan tata letak yang diprar untuk menggambar teks Canvas/Bitmap/PdfDocument.

Anda juga bisa menggunakan Spannable sebagai yourText parameter untuk font yang berbeda juga Htmlstring berformat (seperti pada contoh di bawah ini).

Catatan 2

Ketika semua teks memiliki ukuran font yang sama, semua garis memiliki tinggi yang sama. Dalam hal ini Anda mungkin ingin mempertimbangkan pengoptimalan lebih lanjut dari algoritma dengan menghitung sejumlah garis yang cocok menjadi satu halaman dan melompat ke garis yang tepat pada setiap iterasi putaran.


Mencicipi

Contoh di bawah ini menunjukkan string yang mengandung keduanya html dan Spanned teks.

public class PaginationActivity extends Activity {
    private TextView mTextView;
    private Pagination mPagination;
    private CharSequence mText;
    private int mCurrentIndex = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pagination);

        mTextView = (TextView) findViewById(R.id.tv);

        Spanned htmlString = Html.fromHtml(getString(R.string.html_string));

        Spannable spanString = new SpannableString(getString(R.string.long_string));
        spanString.setSpan(new ForegroundColorSpan(Color.BLUE), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        spanString.setSpan(new RelativeSizeSpan(2f), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        spanString.setSpan(new StyleSpan(Typeface.MONOSPACE.getStyle()), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        spanString.setSpan(new ForegroundColorSpan(Color.BLUE), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        spanString.setSpan(new RelativeSizeSpan(2f), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        spanString.setSpan(new StyleSpan(Typeface.MONOSPACE.getStyle()), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        mText = TextUtils.concat(htmlString, spanString);

        mTextView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                // Removing layout listener to avoid multiple calls
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                    mTextView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                } else {
                    mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
                mPagination = new Pagination(mText,
                        mTextView.getWidth(),
                        mTextView.getHeight(),
                        mTextView.getPaint(),
                        mTextView.getLineSpacingMultiplier(),
                        mTextView.getLineSpacingExtra(),
                        mTextView.getIncludeFontPadding());
                update();
            }
        });

        findViewById(R.id.btn_back).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCurrentIndex = (mCurrentIndex > 0) ? mCurrentIndex - 1 : 0;
                update();
            }
        });
        findViewById(R.id.btn_forward).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCurrentIndex = (mCurrentIndex < mPagination.size() - 1) ? mCurrentIndex + 1 : mPagination.size() - 1;
                update();
            }
        });
    }

    private void update() {
        final CharSequence text = mPagination.get(mCurrentIndex);
        if(text != null) mTextView.setText(text);
    }
}

Activitytata letak:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/btn_back"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/transparent"/>

        <Button
            android:id="@+id/btn_forward"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/transparent"/>

    </LinearLayout>

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

Screenshot: enter image description here


26
2017-08-19 13:27



Lihatlah saya proyek demo.

"Keajaiban" ada dalam kode ini:

    mTextView.setText(mText);

    int height = mTextView.getHeight();
    int scrollY = mTextView.getScrollY();
    Layout layout = mTextView.getLayout();
    int firstVisibleLineNumber = layout.getLineForVertical(scrollY);
    int lastVisibleLineNumber = layout.getLineForVertical(height + scrollY);

    //check is latest line fully visible
    if (mTextView.getHeight() < layout.getLineBottom(lastVisibleLineNumber)) {
        lastVisibleLineNumber--;
    }

    int start = pageStartSymbol + mTextView.getLayout().getLineStart(firstVisibleLineNumber);
    int end = pageStartSymbol + mTextView.getLayout().getLineEnd(lastVisibleLineNumber);

    String displayedText = mText.substring(start, end);
    //correct visible text
    mTextView.setText(displayedText);

6
2017-08-20 09:11



Secara mengejutkan menemukan perpustakaan untuk Paginasi susah. Saya pikir lebih baik menggunakan elemen UI Android lain selain TextView. Bagaimana tentang WebView? Sebuah contoh @ android-webview-example. Potongan kode:

webView = (WebView) findViewById(R.id.webView1);

String customHtml = "<html><body><h1>Hello, WebView</h1></body></html>";
webView.loadData(customHtml, "text/html", "UTF-8");

Catatan: Ini hanya memuat data ke WebView, mirip dengan browser web. Tapi jangan berhenti hanya dengan ide ini. Tambahkan UI ini untuk menggunakan pagination oleh WebViewClient onPageFinished . Silakan baca di SO tautan @ html-book-like-pagination. Potongan kode dari salah satu jawaban terbaik oleh Dan:

mWebView.setWebViewClient(new WebViewClient() {
   public void onPageFinished(WebView view, String url) {
   ...
      mWebView.loadUrl("...");
   }
});

Catatan:

  • Kode memuat lebih banyak data pada halaman gulir.
  • Pada halaman web yang sama, ada jawaban diposting oleh Engin Kurutepe untuk mengatur pengukuran untuk WebView. Ini diperlukan untuk menentukan halaman di pagination.

Saya belum menerapkan paginasi tetapi saya pikir ini adalah awal yang baik dan menunjukkan janji, harus cepat. Seperti yang Anda lihat, ada pengembang yang telah menerapkan fitur ini.


4
2017-08-18 02:27