Thứ Hai, 21 tháng 1, 2013


Tut: Android: Cách chia text thành các trang sử dụng StaticLayout


        Chào các bạn, hnay m sẽ giới thiệu cho các bạn cách chia 1 đoạn text dài (1 chương sách hay cả cuốn sách) thành dạng trang phù hợp với màn hình điện thoại mà bạn đang sử dụng. Sau khi hoàn thành phần hướng dẫn này, các bạn sẽ đạt đc kết quả như sau:
 

I. Giới thiệu về StaticLayout:
+ kế thừa từ class android.text.Layout
+ Constructor:
StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)
StaticLayout có tác dụng là sắp xếp đoạn text của bạn theo layout đã xác lập. Nó sử dụng các tham số:
-          CharSequence  source: đoạn text của bạn
-          TextPaint paint: quy định màu chữ, font, cỡ chữ, style… Và dùng để vẽ text lên canvas (xem thêm về Canvas và các phương thức draw).
-          int width: chiều rộng của trang sách (phần hiển thị chữ, không kể căn lề) mà bạn muốn.
-          Layout.Alignment align:
-          float spacingmult, float spacingadd: khoảng cách dòng,…
-          boolean includepad:
+ StaticLayout “viết” chữ của bạn ntn?
StaticLayout sử dụng phương thức draw(Canvas canvas) để vẽ text lên 1 canvas. Như vậy chúng ta có thể thu được 1 đối tượng Bitmap như sau:

bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// Tạo canvas để vẽ lên
Canvas c = new Canvas(bitmap);
// Vẽ lên canvas
staticlayout.draw(c);

II. Thực hiện:

1. Chuẩn bị:
- Như bạn đã thấy, StaticLayout sẽ vẽ text lên canvas và chúng ta thu đc 1 bitmap. Để đơn giản, m sẽ sử dụng ImageView để view bitmap này.       
- Việc đầu tiên là tạo ra 1 project mới, ở đây m lấy tên là BitmapTest.
- Các bạn sửa layout của main.xml như sau:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <ImageView
            android:id="@+id/imgview"
            android:layout_width="fill_parent"
android:layout_height="fill_parent"
            android:src="#ffFDF8A6"
    ></ImageView>        
</LinearLayout>

▲Ở đây, ImageView với id là image sẽ đảm nhận nhiệm vụ hiển thị bitmap thu đc.

▲Chạy chương trình vào lúc này sẽ cho bạn 1 màn hình màu vàng (ffFDF8A6).
-Tinh chỉnh 1 chút cho chương trình đẹp hơn:
+ Các bạn sửa method onCreate() như sau để chương trình thành full screen:

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);
}

-Tiếp theo, chúng ta cần có 1 đoạn text. Bạn có thể chọn đoạn text bất kỳ nhưng fải đảm bảo đủ dài.

String story = “<nội dung>”

-Khai báo thêm vài thông số cần thiết:

// width, height: chiều dài, rộng màn hình
            int width, height;       
            // Các thông số khác
            int textSize = 20; // cỡ chữ
            int textColor = 0xffA7573E; // màu chữ
            int pageColor = 0xffFDF8A6; // màu nền
            int topPadding = 30, leftPadding = 10; // căn trên, căn dưới
            // Paint vẽ text
      TextPaint myTextPaint;

-Thêm dòng sau vào phương thức onCreate của activity để tìm width, height của màn hình:
Display display = ((WindowManager)this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
            width = display.getWidth();
            height = display.getHeight();

-Khai báo textPaint:
+Bạn có thể thêm phông chữ cho sinh động hơn:

// Xác lập font chữ
Typeface tf = Typeface.createFromAsset(this.getAssets(),"fonts/UVNDaLat_R.TTF");
// Xác lập các thông số về font chữ
myTextPaint = new TextPaint();
            myTextPaint.setColor(textColor);
            myTextPaint.setTextSize(textSize);
            myTextPaint.bgColor = pageColor;
            myTextPaint.setAntiAlias(true);
            myTextPaint.setTypeface(tf);

2.Tiến hành
-Sau đây m sẽ dùng SL để vẽ đoạn text lên màn hình:
//
            public Bitmap getPageBitmap() {                 
                        Bitmap pageContentBitmap;             
                        // Bitmap chứa text
                        pageContentBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                        // Tạo canvas để vẽ lên
                        Canvas c = new Canvas(pageContentBitmap);
                        // Tạo StaticLayout cho nội dung text
                        StaticLayout layout = new StaticLayout(story, myTextPaint, width            - (leftPadding << 1), Layout.Alignment.ALIGN_NORMAL, 0.5f, 10f, false);
                        // Vẽ lên canvas
                        layout.draw(c);
                        return pageContentBitmap;
      }

▲Ở đây, tham số width được truyền vào là width    - (leftPadding << 1) với mục đích căn trái cho đoạn text(chừa lề khi viết, kok viết hết toàn bộ chiều rộng màn hình).

-Hiển thị bitmap lên màn hình:
khai báo và set ảnh cho ImageView:
image = (ImageView) findViewById(R.id.imgview);          
image.setImageBitmap(getPageBitmap());    

▲Bạn chạy chương trình sẽ thấy đoạn text đã đc vẽ lên. Nhg đc vẽ ở vị trí (0,0) của màn hình. Chúng ta cần chỉnh lại 1 chút method getPageBitmap():
//
            public Bitmap getPageBitmap() {                 
                        Bitmap pageBitmap, pageContentBitmap;
                        // Bitmap nền
                        pageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                        // Bitmap chứa text
                        pageContentBitmap = Bitmap.createBitmap(width-(leftPadding<<1), height-topPadding, Bitmap.Config.ARGB_8888);
                        // Tạo canvas để vẽ lên
                        Canvas c = new Canvas(pageContentBitmap);
                        // Tạo StaticLayout cho nội dung text
                        StaticLayout layout = new StaticLayout(story, myTextPaint, width
                                                - (leftPadding << 1), Layout.Alignment.ALIGN_NORMAL, 0.5f, 10f, false);
                        // Vẽ lên canvas
                        layout.draw(c);
                        // Canvas của nền cho vẽ ảnh nội dung lên
                        Canvas c2 = new Canvas(pageBitmap);
                        // Tô màu nền
                        c2.drawColor(pageColor);
                        // vẽ ảnh nội dung
                        c2.drawBitmap(pageContentBitmap, leftPadding<<1, topPadding, myTextPaint);
                        return pageBitmap;
      }

▲Ở đây, chúng ta sẽ dùng 1 bitmap làm nền, sau đó vẽ bitmap chứa text lên vị trí (leftPadding<<1, topPadding). Sau đó trả về bitmap nền này để show lên màn hình.

-Bi h, chạy chương trình sẽ cho kết quả đoạn text được căn chỉnh lại. Tuy nhiên, vẫn có những dòng thừa ở cuối màn hình. Điều này là do SL chỉ có tác dụng WRAP-TEXT nên bạn sẽ thu đc kết quả tương tự như hiển thị text dài trên 1 TextView.

Vậy cách giải quyết vấn đề này ntn?

Nếu bạn muốn cắt bỏ đoạn text thừa ở cuối dòng, thì đoạn text của bạn fải chia nhỏ ra sao cho khớp với kích thước trang. Hay nói cách khác, bạn fải biết đc vị trí cần cắt text. Vị trí đó nằm ổ cuối dòng-dòng cuối cùng của trang (gold position).
Thật may mắn, SL cho chúng ta 2 method:
-          public int getLineForVertical (int vertical): Trả về dòng nằm ở tọa độ (0, vertical)
-          public int getOffsetForHorizontal(int line, float horiz): Trả về index của đoạn text nằm ở dòng line và có tọa độ (horiz,0);
-Và đây là cách mà m đã làm:
//
            public void splitTextIntoPages() {                
                        // các chỉ mục dùng để cắt text
                        int offsetI = 0, offsetII = 0;
                        // Tạo Static Layout cho toàn bộ text
                        StaticLayout layout = new StaticLayout(story, myTextPaint, width
                                                - (leftPadding << 1), Layout.Alignment.ALIGN_NORMAL, 0.5f, 10f, false);
                        // Tổng số dòng
                        int totalLines = layout.getLineCount();
                        // Số dòng từng trang
                        int linePerPage = layout.getLineForVertical(height - (topPadding << 1));
                       
                        // loop to the end
                        int i = 0;
                       
                        do{
                                    //
                                    Log.i("Thong bao", "Dang chia...");
                                    //
                                    int line = Math.min(linePerPage * (i+1), totalLines-1);
                                    // vi tri của ký tự cuối trang
                                    offsetII = layout.getOffsetForHorizontal(line, width - (leftPadding << 1));           
                                    // Lấy subString                                  
                                    String sub = story.substring(offsetI, offsetII);
                                    //
                                    offsetI = offsetII;
                                    // Thêm vào danh sách
                                    listOfPages.add(sub);                        
                                    //
                                    i++;                             
                        }while (offsetII<story.length());                               
            }
▲Ở mỗi vòng lặp, chúng ta sẽ lấy “gold position” và cắt text dựa vào nó. Các đoạn text được cắt (sẽ khớp với kích thước trang) được lưu theo thứ tự vào Vector<String> listOfPages.

-Thử hiển thị trang đầu tiên:
+ method onCreate(): thêm splitTextIntoPages() vào trước dòng set ảnh cho ImageView.
+ method getPageBitmap(): bạn sửa đoạn khai báo SL:
StaticLayout layout = new StaticLayout(listOfPages.elementAt(0), myTextPaint, width- (leftPadding << 1), Layout.Alignment.ALIGN_NORMAL, 0.5f, 10f, false);

▲Chạy thử chương trình sẽ thấy các dòng thừa đã mất.

-Để hiển thị tiếp các trang sau bạn có thể làm như sau:
+ Thêm biến chỉ mục trang: int index=0;
+ method onCreate:
image.setOnClickListener(new OnClickListener() {

                                    @Override
                                    public void onClick(View v) {
                                                //
                                                if (index > listOfPages.size()-1) {
                                                            index = 0;
                                                }                                             
                                                // Xác lập ảnh
                                                image.setImageBitmap(getPageBitmap());                                        
                                                //
                                                index++;
                                                //
                                               
                                    }
                  });
+ method getPageBitmap(): sửa khai báo SL:
StaticLayout layout = new StaticLayout(listOfPages.elementAt(index), myTextPaint, width
                                          - (leftPadding << 1), Layout.Alignment.ALIGN_NORMAL, 0.5f, 10f, false);

Vậy là mỗi khi bạn click chuột vào ảnh, trang mới sẽ đc hiển thị.
Tut đến đây là kết thúc. Hy vọng những thông tin này sẽ giúp ích cho bạn.      
Download source code: http://www.mediafire.com/?bt9p65ud4nf59wm   
sưu tầm từ blog hnimblog

Không có nhận xét nào:

Đăng nhận xét