android listview item點擊展開

最近做項目真是頭疼呢?之前想用ListViewAnnotation來著,就是可以實現類似於android 通知欄滑動刪除的效果。好像是一位大牛自己一個人寫的吧。我在這裡首先向他致敬。不過,話說回來,實現原理也是比較易於理解的,就是檢測你的滑動距離以及速度,再作出判斷,進行操作。具體大傢參考下Google keep的兩種列表模式下滑動刪除的操作就理解類。

非常不幸的事,我再佈局中用瞭fragment和biewpager,所以產生瞭手勢沖突,我為此改寫瞭library裡的手勢操作檢測方法,最後勉強改出來的。但是!!!!效果實在慘不忍睹,隻好作罷,最後,思來想去,我決定使用listview item點擊展開來實現我想要的效果。

好吧,廢話說瞭很多,終於要進入正題瞭。
首先補充三點知識:

第一點:

getLayoutParams()方法和setLayoutParams()用法
首先,我們利用getLayoutParams()來獲得指定控件的LayoutParams。例如:
LinearLayout.LayoutParams lp = (RelativeLayout.LayoutParams) deletButton.getLayoutParams();

然後,我們可以為deleteButton設置layoutparams屬性,例如:
    lp.width = btnWidth;
    lp.leftMargin = 10;
最後,我們將設置好的layoutparams屬性設置給指定的控件:        deletButton.setLayoutParams(lp);

第二點:

public final int getMeasuredHeight ()
Added in API level 1
Like getMeasuredHeightAndState(), but only returns the raw width component (that is the result is masked by MEASURED_SIZE_MASK).

Returns
The raw measured height of this view.
public final int getHeight ()
Added in API level 1
Return the height of your view.

Returns
The height of your view, in pixels.
getMeasuredHeight()返回的是原始測量高度,與屏幕無關,getHeight()返回的是在屏幕上顯示的高度。實際上在當屏幕可以包裹內容的時候,他們的值是相等的,隻有當view超出屏幕後,才能看出他們的區別。當超出屏幕後,getMeasuredHeight()等於getHeight()加上屏幕之外沒有顯示的高度。

第三點:

MeasureSpec的使用。MeasureSpec一般出現在自定義View中,因為在自定義 view中我們經常需要重寫該方法,由它來指定自定義控件在屏幕上的大小。
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
onMeasure傳入的兩個參數是由上一層控件傳入的大小,有多種情況,重寫該方法時需要對計算控件的實際大小,然後調用setMeasuredDimension(int, int)設置實際大小。

onMeasure傳入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸數值,而是將模式和尺寸組合在一起的數值。我們需要通過int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。

mode共有三種情況,取值分別為MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。

MeasureSpec.EXACTLY是精確尺寸,當我們將控件的layout_width或layout_height指定為具體數值時如andorid:layout_width="50dip",或者為FILL_PARENT是,都是控件大小已經確定的情況,都是精確尺寸。

MeasureSpec.AT_MOST是最大尺寸,當控件的layout_width或layout_height指定為WRAP_CONTENT時,控件大小一般隨著控件的子空間或內容進行變化,此時控件尺寸隻要不超過父控件允許的最大尺寸即可。因此,此時的mode是AT_MOST,size給出瞭父控件允許的最大尺寸。

MeasureSpec.UNSPECIFIED是未指定尺寸,這種情況不多,一般都是父控件是AdapterView,通過measure方法傳入的模式。
然後,我們可以調用MeasureSpec.makeMeasureSpec(int size, int mode)來進行設置。

好,現在先上一張demo截圖,使用者可以自己進行擴充。
Alt text
哈哈,效果還行吧。那個,圖標略顯呆萌,還請不要見笑(自己繪制的,苦逼程序員一隻啊)。

好,接下來,我們來看一下動效的實現。當然,先上源碼。

package wenyue.expandlistview.me;

import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout.LayoutParams;

    /**
     * animation
     * 2014年十一月十八日
     * 蒼狼問月
     * canglangwenyue.github.io
     * @author canglangwenyue
     *
     */
public class ViewExpandAnimation extends Animation {

private View mAnimationView = null;
private LayoutParams mViewLayoutParams = null;
private int mStart = 0;
private int mEnd = 0;

public ViewExpandAnimation(View view){
    animationSettings(view, 500);
}

public ViewExpandAnimation(View view, int duration){
    animationSettings(view, duration);
}

private void animationSettings(View view, int duration){
    setDuration(duration);
    mAnimationView = view;
    mViewLayoutParams = (LayoutParams) view.getLayoutParams();
    mStart = mViewLayoutParams.bottomMargin;
    mEnd = (mStart == 0 ? (0 - view.getHeight()) : 0);
    view.setVisibility(View.VISIBLE);
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    super.applyTransformation(interpolatedTime, t);

    if(interpolatedTime < 1.0f){
        mViewLayoutParams.bottomMargin = mStart + (int) ((mEnd - mStart) * interpolatedTime);
        // invalidate
        mAnimationView.requestLayout();
    }else{
        mViewLayoutParams.bottomMargin = mEnd;
        mAnimationView.requestLayout();
        if(mEnd != 0){
            mAnimationView.setVisibility(View.GONE);
        }
    }
    Log.i("hehe", "interpolatedTime = " + interpolatedTime + " , bottomMargin" + 
            mViewLayoutParams.bottomMargin);
}

}

動效實現的代碼不多,setDuration(duration)設置動效的持續時間。之後調用RelitaveLayout.bottomMargin;對於該屬性developer文檔解釋為The bottom margin in pixels of the child.
mAnimationView.requestLayout();該方法內部會調用Object.layout(...)方法,來重會佈局。

至於,listview adapter的重寫,就比較簡單瞭。所以,不再貼出全部源碼。隻說一下需要註意的地方。
首先,你需要在getView(…)中加載自定義listview 的item佈局文件:

convertView = myInflater.inflate(R.layout.expand_item, null);

接下來,貼出相應佈局文件代碼:






    

        
    





    

我設置的是默認在activity onStart時嵌套佈局中的第二個RelativeLayout佈局文件的內容是被隱藏的。
在BaseAdapter中的設置如下:

RelativeLayout footer = (RelativeLayout) convertView
                .findViewById(R.id.footer);
        int widthSpec = MeasureSpec.makeMeasureSpec(
                (int) (mLcdWidth - 10 * mDensity), MeasureSpec.EXACTLY);
        footer.measure(widthSpec, 0);
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) footer
                .getLayoutParams();
        params.bottomMargin = -footer.getMeasuredHeight();
        footer.setVisibility(View.GONE);

然後,是在onCreate方法中設置setOnItemClickListener方法,在其中設置點擊item後,開始動效,再次點擊則隱藏,且同一時間隻有一個item可以展開(因為個人認為用戶不可能在同一時間操作兩個 item的隱藏操作),代碼如下:

if (mLastTouchTag != null) {
                View temp = arg0.findViewWithTag(mLastTouchTag);
                if (temp != null) {
                    View footTemp = temp.findViewById(R.id.footer);
                    if (footTemp != null
                            && (footTemp.getVisibility() != View.GONE)) {
                        footTemp.startAnimation(new ViewExpandAnimation(
                                footTemp));
                    }
                }
            }
            mLastTouchTag = (ViewHolder) v.getTag();
            // onion555 end
            View footer = v.findViewById(R.id.footer);
            footer.startAnimation(new ViewExpandAnimation(footer));

好吧,就寫到這裡吧,有什麼問題,請留言。同樣,在最後附上源碼下載地址。
點擊打開鏈接

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。