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
Bài viết‎ > ‎

Android: Hiệu ứng lật trang không dùng openGL

        Chào các bạn, hwa m đã gt cho các bạn cách chia text thành trang. Theo như đó, bạn có thể làm cho m 1 app đọc sách hoàn chỉnh. Nhg việc đọc như vậy chưa đc hấp dẫn cho lắm. Hni, m sẽ gt cho các bạn cách thêm hiệu ứng lật trang vào app.
       
         Kết quả của bài hni là:


       
        Hwa, m sử dụng ImageView để hiển thị ảnh, hni m sẽ dùng 1 custom view. Việc hiển thị sẽ do method onDraw() đảm nhận. Để đơn jản, m sẽ kok đưa text vào nữa mà sẽ dùng 2 ảnh để thay thế (việc đưa text vào làm như bài hwa). Hiệu ứng chuyển động sẽ do handler đảm nhận.

        Những quan sát ban đầu về việc lật trang sách:



        Khi bạn lật 1 trang sách lên, nếu nhìn từ trên xuống, chúng ta sẽ thấy tương tự như hình H1. Như vậy, nếu muốn vẽ đc hiệu ứng này, ta cần vẽ 3 layer như hình H2: trang ở trên (tr1) sẽ vẽ đầu tiên, sau đó sẽ vẽ 1fần trang 2 ở góc màn hình, cuối cùng là sẽ vẽ fần lật lên.

        Để thực hiện điều này, trong method onDraw() chúng ta dùng 2 method Canvas.clipPath(Path p) và Canvas.drawPath(Path p):
        + clipPath(Path p): cho fép bạn định nghĩa lại vùng vẽ, sau đó khi bạn vẽ bất kỳ hình nào thì chỉ có vùng path này đc hiển thị (Path đc khai báo bằng tọa độ màn hình).
        + drawPath(Path p): method này dùng để vẽ phần lật trang,

        
        Như vậy, muốn vẽ đc bạn cần xác định các Path hay chính là xác định các điểm (tọa độ x, y) tạo nên path. Sau đó, sẽ tao path bằng các method Path.moveTo(), Path.lineTo()... <xem thêm về Path trên trang của android>. Ở đây, chúng ta fải tìm 2 path: path fần lật và path fần hở ra.


        Xác định các điểm đó ntn?

        Khi lật sách lên, các bạn nhìn thấy fần lật có những đường cong, fần lật lên do hiệu ứng 3D sẽ luôn nhỏ hơn fần hiện ra. Để đơn jản, bạn hãy gập fần lật đó xuống(dĩ nhiên khi lật sách kok ai làm vậy cả :D), bi h bạn đã thấy 2 fần bằng nhau (fần lật và fần hở :D).
        
        Khi lật, bạn sẽ thấy đường chéo jao jữa 2 fần chạy từ trái qua fải như cái cần gạt nước của xe otô vậy. Chúng ta sẽ fải chọn lấy 1 gốc của "cần gạt" để tính toán dễ hơn. Dễ nhất là chọn ở các giá trị đã biết và m đã chọn (0, 2 x chiều dài) làm gốc (đây gần như là dk lý tưởng :D).

        Khi đường chéo này chạy, sẽ cắt trang tại 2 điểm, đây cũng chính là 2 điểm của path cần tìm. Để ý rằng, khi lật, trang trên sẽ đi hết fần màn hình, nên m sẽ chọn 1 điểm làm chỉ mục cho bắt đầu và kết thúc lật. Ở đây sẽ là giao của đường chéo với viền dưới của trang (điểm A). khi xA=width -> chưa lật, xA=0->lật xong.

        Hình bên dưới là các fép tính để tìm ra các điểm cần tìm:



           *Chú ý: ban đầu các path đều là hình tam giác, sau đó chuyển thành hình tứ giác. Do đó, để cùng dùng 1 method cho việc tạo path, path tam jác cũng đc tạo từ 4 điêm, vd: path fần hở ra là A-B-C-D, khi còn là tam jác, xD=xC nên ta vẫn có hình tam jác.

            Áp dụng cách tính trên, bạn có thể tạo hiệu ứng lật trang đơn jản. Nhưg vì dùng nhiều điểm cố dịnh nên bạn kok thể "play" với trang sách như video trên đc. Muốn đạt đc đáp ứng như video, bạn cần tạo ra các path fụ thuộc vào vị trí điểm F.

            Cũng với các tính toán như hình trên, chỉ là tính ngc từ F:



        Mã nguồn ở bên dưới là thực hiện đáp ứng theo điểm A, muốn đáp ứng theo điểm F, bạn tìm đoạn sau trong method onDraw():

            if(flipping){
                  pointGenerate(xTouchValue, width, height);
            }else {
                  //pointGenerateII(xTouchValue, yTouchValue, width, height);
                  pointGenerate(xTouchValue, width, height);
            }
    bỏ comment ở pointGenerateII và xóa dòng pointGenerate ở dưới đi.
        
        Bài viết tới đây là kết thúc. Hy vọng sẽ giúp ích cho các bạn.
sưu tầm từ hnimblog

Thứ Năm, 10 tháng 1, 2013

Con đường mới

10 năm so với vài tháng??? thật không đáng để bỏ 10 năm đâu. Cố lên Long, 10 năm đó ăn đứt vài tháng mà

Thứ Ba, 8 tháng 1, 2013

====================Form search====================== Search Form Test ============================================================================================================================================================================== ===============Form Select group ============== ==============================================================================================================================================================================