1. 程式人生 > >仿支付寶錢包:帶分割線的GridView

仿支付寶錢包:帶分割線的GridView

需求:


本文記錄了我嘗試實現支付寶錢包樣式帶分割線GridView的過程。首先看一下高大上的支付寶錢包首頁:

                                                                                   

這裡畫紅框的部分,給人的直觀感覺就是一個GridView 。當然,這裡很可能是支付寶同學自定義了一個GridView的子類 。相對於ListView的單列列表,GridView則是一種支援多行多列的網格形式。 當我們想要實現例如九宮格的效果時,GridView即是首選。

然而,與ListView可以方便的設定和自定義分割線不同不同,GridView貌似並不提供分割線屬性。這樣,只能開動勞動人民的智慧來嘗試解決了。(今天進行了三次嘗試,成功的方式在第三次嘗試中。 歡迎大家提供更好的實現方法)

第一次嘗試:item的背景圖


這個方法實際上是從網上看到的,使用最多的解決方案。 簡而言之,就是對GridView的每個單元做背景圖。程式碼說話:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:layout_margin="0.0dip"
                 >

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_centerInParent="true"
        android:background="@drawable/item_gridview"
        android:padding="12.0dip" >

        <ImageView
            android:id="@+id/gridview_item_icon"
            android:layout_width="58.0dip"
            android:layout_height="58.0dip"
            android:layout_centerHorizontal="true"
            android:contentDescription="@string/app_name" />

        <TextView
            android:id="@+id/gridview_item_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/gridview_item_icon"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="5.0dip"
            android:maxLines="1"
            android:textSize="14.0sp" />
    </RelativeLayout>

</RelativeLayout>  
在item_gridview中,已矩形框為背景:
<selector xmlns:android="http://schemas.android.com/apk/res/android">

        <item android:state_pressed="true"><shape android:shape="rectangle">
            <stroke android:width="1px" android:color="@color/pink" />

            <gradient android:angle="270.0" android:endColor="#ffe8ecef" android:startColor="#ffe8ecef" />
        </shape></item>
        <item android:state_focused="true"><shape android:shape="rectangle">
            <gradient android:angle="270.0" android:endColor="#ffe8ecef" android:startColor="#ffe8ecef" />

            <stroke android:width="1px" android:color="@color/pink" />
        </shape></item>
        <item><shape android:shape="rectangle">
            <gradient android:angle="270.0" android:endColor="#ffffffff" android:startColor="#ffffffff" />

            <stroke android:width="1px" android:color="@color/pink" />
        </shape></item>

</selector>

 
在Activity中對GridView設定Adapter,充入物件,效果如下圖所示。(目前僅探究gridview,其他部分還比較醜)
                                                                               

感覺哪裡不對。。 通過比較中間的列和最右邊的列可以發現,分割線太粗了!
雖然在xml中設定的是最小值 1px , 然而看起來還是比支付寶錢包的效果粗很多。 因為後者才是 真·1px ,前者的分割線實際上是相鄰單元的邊框合併在了一起,所以看起來要粗很多。

第二次嘗試:相鄰單元只有一個背景


上面的問題已經可以看出,是相鄰的單元格背景框相鄰使分割線變粗。 那麼可以提出相應的解決辦法:相鄰單元格只有一個有背景。

以當前4列的情況為例,實際上,可以去掉:
     第一行的第二,第四個單元
     第二行的第一,第三個單元
     第三行的第二,第四個單元
     。。。

可以總結出規律,通過去掉行列數奇偶性不同的單元,可以使相鄰的單元(包括上下相鄰和左右相鄰)不同時具有邊框。在adapter的getView方法中加入如下程式碼:
if(position/4%2!=position%2){
				RelativeLayout layout=(RelativeLayout)convertView.findViewById(R.id.layout_gridview);
				layout.setBackgroundResource(R.drawable.item_gridview2);
}

其中item_gridview2是邊框為空白的背景
效果圖:
                                                                                  
這是才發現,我犯了一個很2的錯誤:小看了1px的誤差
當相鄰單元的邊框去除後,成對角方向的單元框對不齊了。。 

第三次嘗試:暴力加框


這一次比較直接和暴力,最終達到了預想的效果。但是顯得不是那麼聰明和巧妙,這裡拋磚引玉,希望路過的大牛能給出更好的實現方法。

這一次,通過在單元格xml中插入兩個寬度為1px的ImageView做邊框,來實現gridview的偽分割線:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

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


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:id="@+id/layout_gridview_item"
        android:orientation="vertical"
         >

        <ImageView
            android:layout_marginTop="12dp"
            android:id="@+id/gridview_item_icon"
            android:layout_width="38.0dip"
            android:layout_height="38.0dip"
            android:layout_centerHorizontal="true"
            android:background="@drawable/guan"
            android:contentDescription="@string/app_name" />

        <TextView
            android:id="@+id/gridview_item_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/gridview_item_icon"
            android:layout_centerHorizontal="true"
            android:maxLines="1"
            android:text="標題"
            android:textColor="@color/darkblue"
            android:layout_marginBottom="12dp"
            android:textSize="14.0sp" />

        <ImageView
            android:layout_width="match_parent"
            android:id="@+id/line_bottom"
            android:layout_height="1dp"
            android:background="@color/lightgray"
            android:layout_gravity="bottom"/>


    </LinearLayout>

        <ImageView
            android:layout_width="1dp"
            android:layout_weight="0"
            android:layout_height="match_parent"
            android:background="@color/lightgray"
            android:layout_gravity="bottom"
            />

    </LinearLayout>

</LinearLayout>
 最終效果。。
                                                                                 

第四次嘗試:重寫dispatchDraw 方法


這裡我們可以對gridview進行重繪, 重寫父類的dispatchDraw方法。
思路基本跟方法三一致,對每個子item畫兩條框。不同之處在於,當我們採用動態繪製(而不是寫死在xml)時,可以針對不同的情況進行判斷和定製,使最後的效果更完美。 在這裡,可以考慮到左右上下的對稱性和gridview的item數不是列數的整數倍時的情況。 重寫程式碼如下:
@Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        View localView1 = getChildAt(0);
        int column = getWidth() / localView1.getWidth();
        int childCount = getChildCount();
        Paint localPaint= new Paint();
        localPaint.setStyle(Paint.Style.STROKE);
        localPaint.setColor(getContext().getResources().getColor(R.color.lightgray));
        for(int i = 0;i < childCount;i++){
            View cellView = getChildAt(i);
            if((i + 1) % column == 0){ //最右側只畫底部邊框
                canvas.drawLine(cellView.getLeft(), cellView.getBottom(), cellView.getRight(), cellView.getBottom(), localPaint);
            }else if((i + 1) > (childCount - (childCount % column))){//最下部只畫右側邊框
                canvas.drawLine(cellView.getRight(), cellView.getTop(), cellView.getRight(), cellView.getBottom(), localPaint);
            }else{//畫底部和右側邊框
                canvas.drawLine(cellView.getRight(), cellView.getTop(), cellView.getRight(), cellView.getBottom(), localPaint);
                canvas.drawLine(cellView.getLeft(), cellView.getBottom(), cellView.getRight(), cellView.getBottom(), localPaint);
            }
        }
        //如果最後一行不足,則補全預設item的右側豎線。
        if(childCount % column != 0){
            for(int j = 0 ;j < (column-childCount % column) ; j++){
                View lastView = getChildAt(childCount - 1);
                canvas.drawLine(lastView.getRight() + lastView.getWidth() * j, lastView.getTop(), lastView.getRight() + lastView.getWidth()* j, lastView.getBottom(), localPaint);
            }
        }
    }

 
效果與第三種方法相同。