自定义控件-下拉刷新和上拉加载的listView

0.058字数 961阅读 2134

本博客原地址:http://www.jianshu.com/p/cf4a77727d68
本博客为,下拉刷新和上拉加载的listview的实现。
本项目github地址:
https://github.com/CameloeAnthony/DropDownListViewDemo

(1)效果展示:

首先看下大致的效果:


下拉刷新和上拉加载的listview

(2) 具体使用:

接着看一下怎么使用的:
2.1 在layout中定义

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

    <com.nsu.app.dropdownlistviewdemo.DropDownListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:drawSelectorOnTop="false"
        listViewAttr:isAutoLoadOnBottom="true"
        listViewAttr:isDropDownStyle="true"
        listViewAttr:isOnBottomStyle="true" />
</RelativeLayout>

DropDownListView自定义了三个boolean属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="drop_down_list_attr">
        <attr name="isDropDownStyle" format="boolean" />
        <attr name="isOnBottomStyle" format="boolean" />
        <attr name="isAutoLoadOnBottom" format="boolean" />
    </declare-styleable>
</resources>

isDropDownStyle表示是否允许下拉样式,java代码中可自定义下拉listener,表示需要完成的任务
isOnBottomStyle表示是否允许底部样式,java代码中可自定义滚动到底部的listener,表示需要完成的任务
isAutoLoadOnBottom表示是否允许滚动到底部时自动执行对应listener,仅在isOnBottomStyle为true时有效
PS:如果isDropDownStyle或isOnBottomStyle为false,并不会加载对应的布局,所以性能同ListView一样。
2.2 在java类中调用:
示例代码如下:

package com.nsu.app.dropdownlistviewdemo;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;

/**
 * Create By Anthony on 2016/2/15
 * demo of dropDownListView
 */
public class DropDownListViewDemo extends Activity {
    private LinkedList<String> listItems           = null;
    private DropDownListView listView            = null;
    private ArrayAdapter<String> adapter;

    private String[]             mStrings            = {"11111", "2222", "3333",
            "4444", "5555", "6666", "77777", "8888", "99999",
            "aaaaa", "bbbb", "cccc", "dddd", "eeee",};
    public static final int      MORE_DATA_MAX_COUNT = 3;
    public int                   moreDataCount       = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drop_down_list_view_demo);

        listView = (DropDownListView)findViewById(R.id.list_view);
        // set drop down listener
        listView.setOnDropDownListener(new DropDownListView.OnDropDownListener() {

            @Override
            public void onDropDown() {
                new GetDataTask(true).execute();
            }
        });

        // set on bottom listener
        listView.setOnBottomListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                new GetDataTask(false).execute();
            }
        });

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//                ToastUtils.show(context, R.string.drop_down_tip);
                Toast.makeText(DropDownListViewDemo.this,R.string.drop_down_tip,Toast.LENGTH_LONG).show();
            }
        });
        // listView.setShowFooterWhenNoMore(true);

        listItems = new LinkedList<String>();
        listItems.addAll(Arrays.asList(mStrings));
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listItems);
        listView.setAdapter(adapter);
    }

    private class GetDataTask extends AsyncTask<Void, Void, String[]> {

        private boolean isDropDown;

        public GetDataTask(boolean isDropDown) {
            this.isDropDown = isDropDown;
        }

        @Override
        protected String[] doInBackground(Void... params) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                
            }
            return mStrings;
        }

        @Override
        protected void onPostExecute(String[] result) {

            if (isDropDown) {
                listItems.addFirst("Added after drop down");
                adapter.notifyDataSetChanged();

                // should call onDropDownComplete function of DropDownListView at end of drop down complete.
                SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss");
                listView.onDropDownComplete(getString(R.string.update_at) + dateFormat.format(new Date()));
            } else {
                moreDataCount++;
                listItems.add("Added after on bottom");
                adapter.notifyDataSetChanged();

                if (moreDataCount >= MORE_DATA_MAX_COUNT) {
                    listView.setHasMore(false);
                }

                // should call onBottomComplete function of DropDownListView at end of on bottom complete.
                listView.onBottomComplete();
            }

            super.onPostExecute(result);
        }
    }

}

可以看到我们通过setOnDropDownListener设置下拉的事件,不过需要在事件结束时手动调用onDropDownComplete恢复状态(注意需要在adapter.notifyDataSetChanged();后面调用)。
通过setOnBottomListener设置滚动到底部的事件,不过需要在事件结束时手动调用onBottomComplete恢复状态。

(3) 相关属性的定义:

样式的定义(header 和footer)
我们是在listview的上面加上了header和下面加上了footer,那么来看看我们的自定义属性:
3.1自定义下拉的图片,在项目drawable资源下添加文件名为drop_down_list_arrow.png的图片即可
3.2定义相关文字,strings.xml中定义下面属性:

<resources>
    <string name="app_name">DropDownListViewDemo</string>
    <string name="action_settings">Settings</string>
    <string name="image_content">Image</string>

    <string name="drop_down_list_header_default_text">Click to refresh。。。</string>
    <string name="drop_down_list_header_pull_text">Drop Down to refresh。。。</string>
    <string name="drop_down_list_header_release_text">Release to refresh。。。</string>
    <string name="drop_down_list_header_loading_text">Loading。。。</string>
    <string name="drop_down_list_footer_default_text">More</string>
    <string name="drop_down_list_footer_loading_text">Loading。。。</string>
    <string name="drop_down_list_footer_no_more_text">No More</string>

    <string name="drop_down_tip">Pull down to refresh or scroll to bottom to load more</string>
    <string name="update_at">updated at:</string>
</resources>

3.3定义相关字体颜色,colors.xml中定义下面属性:

<!-- drop down list header font color -->
<color name="drop_down_list_header_font_color">#000000</color>
<!-- drop down list header second font color -->
<color name="drop_down_list_header_second_font_color">#000000</color>
<!-- drop down list footer font color -->
<color name="drop_down_list_footer_font_color">#000000</color>

3.4 定义相关样式(会覆盖前面的string和color定义),styles.xml中定义下面属性:

<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <style name="AppTheme" parent="@android:style/Theme.Holo.Light">
    </style>
    <style name="drop_down_list_header_progress_bar_style">
        <item name="android:minHeight">@dimen/drop_down_list_header_progress_bar_height</item>
        <item name="android:maxHeight">@dimen/drop_down_list_header_progress_bar_height</item>
    </style>

    <style name="drop_down_list_footer_progress_bar_style">
        <item name="android:minHeight">@dimen/drop_down_list_footer_progress_bar_height</item>
        <item name="android:maxHeight">@dimen/drop_down_list_footer_progress_bar_height</item>
    </style>

    <style name="drop_down_list_header_font_style">
        <item name="android:textColor">@color/drop_down_list_header_font_color</item>
        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
    </style>

    <style name="drop_down_list_header_second_font_style">
        <item name="android:textColor">@color/drop_down_list_header_second_font_color</item>
        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
    </style>

    <style name="drop_down_list_footer_font_style">
        <item name="android:textColor">@color/drop_down_list_footer_font_color</item>
        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
    </style>
</resources>

3.4 定义相关dimen值,dimens.xml中定义下面属性:

<dimen name="drop_down_list_header_padding_top">12dp</dimen>
<dimen name="drop_down_list_header_padding_bottom">15dp</dimen>
<dimen name="drop_down_list_header_progress_bar_height">36dp</dimen>
<dimen name="drop_down_list_footer_progress_bar_height">36dp</dimen>

(4)核心代码:

也是最重要的我们的自定义控件:

package com.nsu.app.dropdownlistviewdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

/**
 * ListView which can drop down to refresh and
 * load more when scroll to bottom
 */
public class DropDownListView extends ListView implements OnScrollListener {

    private boolean            isDropDownStyle         = true;
    private boolean            isOnBottomStyle         = true;
    private boolean            isAutoLoadOnBottom      = false;

    private String             headerDefaultText;
    private String             headerPullText;
    private String             headerReleaseText;
    private String             headerLoadingText;
    private String             footerDefaultText;
    private String             footerLoadingText;
    private String             footerNoMoreText;

    private Context            context;

    /** header layout view **/
    private RelativeLayout     headerLayout;
    private ImageView          headerImage;
    private ProgressBar        headerProgressBar;
    private TextView           headerText;
    private TextView           headerSecondText;

    /** footer layout view **/
    private RelativeLayout     footerLayout;
    private ProgressBar        footerProgressBar;
    private Button             footerButton;

    private OnDropDownListener onDropDownListener;
    private OnScrollListener   onScrollListener;

    /** rate about drop down distance and header padding top when drop down **/
    private float              headerPaddingTopRate    = 1.5f;
    /** min distance which header can release to loading **/
    private int                headerReleaseMinDistance;

    /** whether bottom listener has more **/
    private boolean            hasMore                 = true;
    /** whether show footer loading progress bar when loading **/
    private boolean            isShowFooterProgressBar = true;
    /** whether show footer when no more data **/
    private boolean            isShowFooterWhenNoMore  = false;

    private int                currentScrollState;
    private int                currentHeaderStatus;

    /** whether reached top, when has reached top, don't show header layout **/
    private boolean            hasReachedTop           = false;

    /** image flip animation **/
    private RotateAnimation    flipAnimation;
    /** image reverse flip animation **/
    private RotateAnimation    reverseFlipAnimation;

    /** header layout original height **/
    private int                headerOriginalHeight;
    /** header layout original padding top **/
    private int                headerOriginalTopPadding;
    /** y of point which user touch down **/
    private float              actionDownPointY;

    /** whether is on bottom loading **/
    private boolean            isOnBottomLoading       = false;

    public DropDownListView(Context context) {
        super(context);
        init(context);
    }

    public DropDownListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        getAttrs(context, attrs);
        init(context);
    }

    public DropDownListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        getAttrs(context, attrs);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        initDropDownStyle();
        initOnBottomStyle();

        // should set, to run onScroll method and so on
        super.setOnScrollListener(this);
    }

    /**
     * init drop down style, only init once
     */
    private void initDropDownStyle() {
        if (headerLayout != null) {
            if (isDropDownStyle) {
                addHeaderView(headerLayout);
            } else {
                removeHeaderView(headerLayout);
            }
            return;
        }
        if (!isDropDownStyle) {
            return;
        }

        headerReleaseMinDistance = context.getResources().getDimensionPixelSize(
                R.dimen.drop_down_list_header_release_min_distance);
        flipAnimation = new RotateAnimation(0, 180, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        flipAnimation.setInterpolator(new LinearInterpolator());
        flipAnimation.setDuration(250);
        flipAnimation.setFillAfter(true);
        reverseFlipAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        reverseFlipAnimation.setInterpolator(new LinearInterpolator());
        reverseFlipAnimation.setDuration(250);
        reverseFlipAnimation.setFillAfter(true);

        headerDefaultText = context.getString(R.string.drop_down_list_header_default_text);
        headerPullText = context.getString(R.string.drop_down_list_header_pull_text);
        headerReleaseText = context.getString(R.string.drop_down_list_header_release_text);
        headerLoadingText = context.getString(R.string.drop_down_list_header_loading_text);

        LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        headerLayout = (RelativeLayout)inflater.inflate(R.layout.drop_down_list_header, this, false);
        headerText = (TextView)headerLayout.findViewById(R.id.drop_down_list_header_default_text);
        headerImage = (ImageView)headerLayout.findViewById(R.id.drop_down_list_header_image);
        headerProgressBar = (ProgressBar)headerLayout.findViewById(R.id.drop_down_list_header_progress_bar);
        headerSecondText = (TextView)headerLayout.findViewById(R.id.drop_down_list_header_second_text);
        headerLayout.setClickable(true);
        headerLayout.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                onDropDown();
            }
        });
        headerText.setText(headerDefaultText);
        addHeaderView(headerLayout);

        measureHeaderLayout(headerLayout);
        headerOriginalHeight = headerLayout.getMeasuredHeight();
        headerOriginalTopPadding = headerLayout.getPaddingTop();
        currentHeaderStatus = HEADER_STATUS_CLICK_TO_LOAD;
    }

    /**
     * init on bottom style, only init once
     */
    private void initOnBottomStyle() {
        if (footerLayout != null) {
            if (isOnBottomStyle) {
                addFooterView(footerLayout);
            } else {
                removeFooterView(footerLayout);
            }
            return;
        }
        if (!isOnBottomStyle) {
            return;
        }

        footerDefaultText = context.getString(R.string.drop_down_list_footer_default_text);
        footerLoadingText = context.getString(R.string.drop_down_list_footer_loading_text);
        footerNoMoreText = context.getString(R.string.drop_down_list_footer_no_more_text);

        LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        footerLayout = (RelativeLayout)inflater.inflate(R.layout.drop_down_list_footer, this, false);
        footerButton = (Button)footerLayout.findViewById(R.id.drop_down_list_footer_button);
        footerButton.setDrawingCacheBackgroundColor(0);
        footerButton.setEnabled(true);

        footerProgressBar = (ProgressBar)footerLayout.findViewById(R.id.drop_down_list_footer_progress_bar);
        addFooterView(footerLayout);
    }

    /**
     * @return isDropDownStyle
     */
    public boolean isDropDownStyle() {
        return isDropDownStyle;
    }

    /**
     * @param isDropDownStyle
     */
    public void setDropDownStyle(boolean isDropDownStyle) {
        if (this.isDropDownStyle != isDropDownStyle) {
            this.isDropDownStyle = isDropDownStyle;
            initDropDownStyle();
        }
    }

    /**
     * @return isOnBottomStyle
     */
    public boolean isOnBottomStyle() {
        return isOnBottomStyle;
    }

    /**
     * @param isOnBottomStyle
     */
    public void setOnBottomStyle(boolean isOnBottomStyle) {
        if (this.isOnBottomStyle != isOnBottomStyle) {
            this.isOnBottomStyle = isOnBottomStyle;
            initOnBottomStyle();
        }
    }

    /**
     * @return isAutoLoadOnBottom
     */
    public boolean isAutoLoadOnBottom() {
        return isAutoLoadOnBottom;
    }

    /**
     * set whether auto load when on bottom
     * 
     * @param isAutoLoadOnBottom
     */
    public void setAutoLoadOnBottom(boolean isAutoLoadOnBottom) {
        this.isAutoLoadOnBottom = isAutoLoadOnBottom;
    }

    /**
     * get whether show footer loading progress bar when loading
     * 
     * @return the isShowFooterProgressBar
     */
    public boolean isShowFooterProgressBar() {
        return isShowFooterProgressBar;
    }

    /**
     * set whether show footer loading progress bar when loading
     * 
     * @param isShowFooterProgressBar
     */
    public void setShowFooterProgressBar(boolean isShowFooterProgressBar) {
        this.isShowFooterProgressBar = isShowFooterProgressBar;
    }

    /**
     * get isShowFooterWhenNoMore
     * 
     * @return the isShowFooterWhenNoMore
     */
    public boolean isShowFooterWhenNoMore() {
        return isShowFooterWhenNoMore;
    }

    /**
     * set isShowFooterWhenNoMore
     * 
     * @param isShowFooterWhenNoMore the isShowFooterWhenNoMore to set
     */
    public void setShowFooterWhenNoMore(boolean isShowFooterWhenNoMore) {
        this.isShowFooterWhenNoMore = isShowFooterWhenNoMore;
    }

    /**
     * get footer button
     * 
     * @return
     */
    public Button getFooterButton() {
        return footerButton;
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(adapter);
        if (isDropDownStyle) {
            setSecondPositionVisible();
        }
    }

    @Override
    public void setOnScrollListener(OnScrollListener listener) {
        onScrollListener = listener;
    }

    /**
     * @param onDropDownListener
     */
    public void setOnDropDownListener(OnDropDownListener onDropDownListener) {
        this.onDropDownListener = onDropDownListener;
    }

    /**
     * @param onBottomListener
     */
    public void setOnBottomListener(OnClickListener onBottomListener) {
        if (!isDropDownStyle) {
            throw new RuntimeException(
                    "isDropDownStyle is false, cannot call setOnBottomListener, you can call setDropDownStyle(true) at fitst.");
        }
        footerButton.setOnClickListener(onBottomListener);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isDropDownStyle) {
            return super.onTouchEvent(event);
        }

        hasReachedTop = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                actionDownPointY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                adjustHeaderPadding(event);
                break;
            case MotionEvent.ACTION_UP:
                if (!isVerticalScrollBarEnabled()) {
                    setVerticalScrollBarEnabled(true);
                }
                /**
                 * set status when finger leave screen if first item visible and header status is not
                 * HEADER_STATUS_LOADING
                 * <ul>
                 * <li>if current header status is HEADER_STATUS_RELEASE_TO_LOAD, call onDropDown.</li>
                 * <li>if current header status is HEADER_STATUS_DROP_DOWN_TO_LOAD, then set header status to
                 * HEADER_STATUS_CLICK_TO_LOAD and hide header layout.</li>
                 * </ul>
                 */
                if (getFirstVisiblePosition() == 0 && currentHeaderStatus != HEADER_STATUS_LOADING) {
                    switch (currentHeaderStatus) {
                        case HEADER_STATUS_RELEASE_TO_LOAD:
                            onDropDown();
                            break;
                        case HEADER_STATUS_DROP_DOWN_TO_LOAD:
                            setHeaderStatusClickToLoad();
                            setSecondPositionVisible();
                            break;
                        case HEADER_STATUS_CLICK_TO_LOAD:
                        default:
                            break;
                    }
                }
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (isDropDownStyle) {
            if (currentScrollState == SCROLL_STATE_TOUCH_SCROLL && currentHeaderStatus != HEADER_STATUS_LOADING) {
                /**
                 * when state of ListView is SCROLL_STATE_TOUCH_SCROLL(ListView is scrolling and finger is on screen)
                 * and header status is not HEADER_STATUS_LOADING
                 * <ul>
                 * if header layout is visiable,
                 * <li>if height of header is higher than a fixed value, then set header status to
                 * HEADER_STATUS_RELEASE_TO_LOAD.</li>
                 * <li>else set header status to HEADER_STATUS_DROP_DOWN_TO_LOAD.</li>
                 * </ul>
                 * <ul>
                 * if header layout is not visiable,
                 * <li>set header status to HEADER_STATUS_CLICK_TO_LOAD.</li>
                 * </ul>
                 */
                if (firstVisibleItem == 0) {
                    headerImage.setVisibility(View.VISIBLE);
                    int pointBottom = headerOriginalHeight + headerReleaseMinDistance;
                    if (headerLayout.getBottom() >= pointBottom) {
                        setHeaderStatusReleaseToLoad();
                    } else if (headerLayout.getBottom() < pointBottom) {
                        setHeaderStatusDropDownToLoad();
                    }
                } else {
                    setHeaderStatusClickToLoad();
                }
            } else if (currentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0
                    && currentHeaderStatus != HEADER_STATUS_LOADING) {
                /**
                 * when state of ListView is SCROLL_STATE_FLING(ListView is scrolling but finger has leave screen) and
                 * first item(header layout) is visible and header status is not HEADER_STATUS_LOADING, then hide first
                 * item, set second item visible and set hasReachedTop true.
                 */
                setSecondPositionVisible();
                hasReachedTop = true;
            } else if (currentScrollState == SCROLL_STATE_FLING && hasReachedTop) {
                /**
                 * when state of ListView is SCROLL_STATE_FLING(ListView is scrolling but finger has leave screen) and
                 * hasReachedTop is true(it's because flick back), then hide first item, set second item visible.
                 */
                setSecondPositionVisible();
            }
        }

        // if isOnBottomStyle and isAutoLoadOnBottom and hasMore, then call onBottom function auto
        if (isOnBottomStyle && isAutoLoadOnBottom && hasMore) {
            if (firstVisibleItem > 0 && totalItemCount > 0 && (firstVisibleItem + visibleItemCount == totalItemCount)) {
                onBottom();
            }
        }
        if (onScrollListener != null) {
            onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (isDropDownStyle) {
            currentScrollState = scrollState;

            if (currentScrollState == SCROLL_STATE_IDLE) {
                hasReachedTop = false;
            }
        }

        if (onScrollListener != null) {
            onScrollListener.onScrollStateChanged(view, scrollState);
        }
    }

    /**
     * drop down begin, adjust view status
     */
    private void onDropDownBegin() {
        if (isDropDownStyle) {
            setHeaderStatusLoading();
        }
    }

    /**
     * on drop down loading, you can call it by manual, but you should manual call onBottomComplete at the same time.
     */
    public void onDropDown() {
        if (currentHeaderStatus != HEADER_STATUS_LOADING && isDropDownStyle && onDropDownListener != null) {
            onDropDownBegin();
            onDropDownListener.onDropDown();
        }
    }

    /**
     * drop down complete, restore view status
     * 
     * @param secondText display below header text, if null, not display
     */
    public void onDropDownComplete(CharSequence secondText) {
        if (isDropDownStyle) {
            setHeaderSecondText(secondText);
            onDropDownComplete();
        }
    }

    /**
     * set header second text
     * 
     * @param secondText secondText display below header text, if null, not display
     */
    public void setHeaderSecondText(CharSequence secondText) {
        if (isDropDownStyle) {
            if (secondText == null) {
                headerSecondText.setVisibility(View.GONE);
            } else {
                headerSecondText.setVisibility(View.VISIBLE);
                headerSecondText.setText(secondText);
            }
        }
    }

    /**
     * drop down complete, restore view status
     */
    public void onDropDownComplete() {
        if (isDropDownStyle) {
            setHeaderStatusClickToLoad();

            if (headerLayout.getBottom() > 0) {
                invalidateViews();
                setSecondPositionVisible();
            }
        }
    }

    /**
     * on bottom begin, adjust view status
     */
    private void onBottomBegin() {
        if (isOnBottomStyle) {
            if (isShowFooterProgressBar) {
                footerProgressBar.setVisibility(View.VISIBLE);
            }
            footerButton.setText(footerLoadingText);
            footerButton.setEnabled(false);
        }
    }

    /**
     * on bottom loading, you can call it by manual, but you should manual call onBottomComplete at the same time.
     */
    public void onBottom() {
        if (isOnBottomStyle && !isOnBottomLoading) {
            isOnBottomLoading = true;
            onBottomBegin();
            footerButton.performClick();
        }
    }

    /**
     * on bottom load complete, restore view status
     */
    public void onBottomComplete() {
        if (isOnBottomStyle) {
            if (isShowFooterProgressBar) {
                footerProgressBar.setVisibility(View.GONE);
            }
            if (!hasMore) {
                footerButton.setText(footerNoMoreText);
                footerButton.setEnabled(false);
                if (!isShowFooterWhenNoMore) {
                    removeFooterView(footerLayout);
                }
            } else {
                footerButton.setText(footerDefaultText);
                footerButton.setEnabled(true);
            }
            isOnBottomLoading = false;
        }
    }

    /**
     * OnDropDownListener, called when header released
     * 
     * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2012-5-31
     */
    public interface OnDropDownListener {

        /**
         * called when header released
         */
        public void onDropDown();
    }

    /**
     * set second position visible(index is 1), because first position is header layout
     */
    public void setSecondPositionVisible() {
        if (getAdapter() != null && getAdapter().getCount() > 0 && getFirstVisiblePosition() == 0) {
            setSelection(1);
        }
    }

    /**
     * set whether has more. if hasMore is false, onBottm will not be called when listView scroll to bottom
     * 
     * @param hasMore
     */
    public void setHasMore(boolean hasMore) {
        this.hasMore = hasMore;
    }

    /**
     * get whether has more
     * 
     * @return
     */
    public boolean isHasMore() {
        return hasMore;
    }

    /**
     * get header layout view
     * 
     * @return
     */
    public RelativeLayout getHeaderLayout() {
        return headerLayout;
    }

    /**
     * get footer layout view
     * 
     * @return
     */
    public RelativeLayout getFooterLayout() {
        return footerLayout;
    }

    /**
     * get rate about drop down distance and header padding top when drop down
     * 
     * @return headerPaddingTopRate
     */
    public float getHeaderPaddingTopRate() {
        return headerPaddingTopRate;
    }

    /**
     * set rate about drop down distance and header padding top when drop down
     * 
     * @param headerPaddingTopRate
     */
    public void setHeaderPaddingTopRate(float headerPaddingTopRate) {
        this.headerPaddingTopRate = headerPaddingTopRate;
    }

    /**
     * get min distance which header can release to loading
     * 
     * @return headerReleaseMinDistance
     */
    public int getHeaderReleaseMinDistance() {
        return headerReleaseMinDistance;
    }

    /**
     * set min distance which header can release to loading
     * 
     * @param headerReleaseMinDistance
     */
    public void setHeaderReleaseMinDistance(int headerReleaseMinDistance) {
        this.headerReleaseMinDistance = headerReleaseMinDistance;
    }

    /***
     * get header default text, default is R.string.drop_down_list_header_default_text
     * 
     * @return
     */
    public String getHeaderDefaultText() {
        return headerDefaultText;
    }

    /**
     * set header default text, default is R.string.drop_down_list_header_default_text
     * 
     * @param headerDefaultText
     */
    public void setHeaderDefaultText(String headerDefaultText) {
        this.headerDefaultText = headerDefaultText;
        if (headerText != null && currentHeaderStatus == HEADER_STATUS_CLICK_TO_LOAD) {
            headerText.setText(headerDefaultText);
        }
    }

    /**
     * get header pull text, default is R.string.drop_down_list_header_pull_text
     * 
     * @return
     */
    public String getHeaderPullText() {
        return headerPullText;
    }

    /**
     * set header pull text, default is R.string.drop_down_list_header_pull_text
     * 
     * @param headerPullText
     */
    public void setHeaderPullText(String headerPullText) {
        this.headerPullText = headerPullText;
    }

    /**
     * get header release text, default is R.string.drop_down_list_header_release_text
     * 
     * @return
     */
    public String getHeaderReleaseText() {
        return headerReleaseText;
    }

    /**
     * set header release text, default is R.string.drop_down_list_header_release_text
     * 
     * @param headerReleaseText
     */
    public void setHeaderReleaseText(String headerReleaseText) {
        this.headerReleaseText = headerReleaseText;
    }

    /**
     * get header loading text, default is R.string.drop_down_list_header_loading_text
     * 
     * @return
     */
    public String getHeaderLoadingText() {
        return headerLoadingText;
    }

    /**
     * set header loading text, default is R.string.drop_down_list_header_loading_text
     * 
     * @param headerLoadingText
     */
    public void setHeaderLoadingText(String headerLoadingText) {
        this.headerLoadingText = headerLoadingText;
    }

    /**
     * get footer default text, default is R.string.drop_down_list_footer_default_text
     * 
     * @return
     */
    public String getFooterDefaultText() {
        return footerDefaultText;
    }

    /**
     * set footer default text, default is R.string.drop_down_list_footer_default_text
     * 
     * @param footerDefaultText
     */
    public void setFooterDefaultText(String footerDefaultText) {
        this.footerDefaultText = footerDefaultText;
        if (footerButton != null && footerButton.isEnabled()) {
            footerButton.setText(footerDefaultText);
        }
    }

    /**
     * get footer loading text, default is R.string.drop_down_list_footer_loading_text
     * 
     * @return
     */
    public String getFooterLoadingText() {
        return footerLoadingText;
    }

    /**
     * set footer loading text, default is R.string.drop_down_list_footer_loading_text
     * 
     * @param footerLoadingText
     */
    public void setFooterLoadingText(String footerLoadingText) {
        this.footerLoadingText = footerLoadingText;
    }

    /**
     * get footer no more text, default is R.string.drop_down_list_footer_no_more_text
     * 
     * @return
     */
    public String getFooterNoMoreText() {
        return footerNoMoreText;
    }

    /**
     * set footer no more text, default is R.string.drop_down_list_footer_no_more_text
     * 
     * @param footerNoMoreText
     */
    public void setFooterNoMoreText(String footerNoMoreText) {
        this.footerNoMoreText = footerNoMoreText;
    }

    /** status which you can click to load, init satus **/
    public static final int HEADER_STATUS_CLICK_TO_LOAD     = 1;
    /**
     * status which you can drop down and then release to excute onDropDownListener, when height of header layout lower
     * than a value
     **/
    public static final int HEADER_STATUS_DROP_DOWN_TO_LOAD = 2;
    /** status which you can release to excute onDropDownListener, when height of header layout higher than a value **/
    public static final int HEADER_STATUS_RELEASE_TO_LOAD   = 3;
    /** status which is loading **/
    public static final int HEADER_STATUS_LOADING           = 4;

    /**
     * set header status to {@link #HEADER_STATUS_CLICK_TO_LOAD}
     */
    private void setHeaderStatusClickToLoad() {
        if (currentHeaderStatus != HEADER_STATUS_CLICK_TO_LOAD) {
            resetHeaderPadding();

            headerImage.clearAnimation();
            headerImage.setVisibility(View.GONE);
            headerProgressBar.setVisibility(View.GONE);
            headerText.setText(headerDefaultText);

            currentHeaderStatus = HEADER_STATUS_CLICK_TO_LOAD;
        }
    }

    /**
     * set header status to {@link #HEADER_STATUS_DROP_DOWN_TO_LOAD}
     */
    private void setHeaderStatusDropDownToLoad() {
        if (currentHeaderStatus != HEADER_STATUS_DROP_DOWN_TO_LOAD) {
            headerImage.setVisibility(View.VISIBLE);
            if (currentHeaderStatus != HEADER_STATUS_CLICK_TO_LOAD) {
                headerImage.clearAnimation();
                headerImage.startAnimation(reverseFlipAnimation);
            }
            headerProgressBar.setVisibility(View.GONE);
            headerText.setText(headerPullText);

            if (isVerticalFadingEdgeEnabled()) {
                setVerticalScrollBarEnabled(false);
            }

            currentHeaderStatus = HEADER_STATUS_DROP_DOWN_TO_LOAD;
        }
    }

    /**
     * set header status to {@link #HEADER_STATUS_RELEASE_TO_LOAD}
     */
    private void setHeaderStatusReleaseToLoad() {
        if (currentHeaderStatus != HEADER_STATUS_RELEASE_TO_LOAD) {
            headerImage.setVisibility(View.VISIBLE);
            headerImage.clearAnimation();
            headerImage.startAnimation(flipAnimation);
            headerProgressBar.setVisibility(View.GONE);
            headerText.setText(headerReleaseText);

            currentHeaderStatus = HEADER_STATUS_RELEASE_TO_LOAD;
        }
    }

    /**
     * set header status to {@link #HEADER_STATUS_LOADING}
     */
    private void setHeaderStatusLoading() {
        if (currentHeaderStatus != HEADER_STATUS_LOADING) {
            resetHeaderPadding();

            headerImage.setVisibility(View.GONE);
            headerImage.clearAnimation();
            headerProgressBar.setVisibility(View.VISIBLE);
            headerText.setText(headerLoadingText);

            currentHeaderStatus = HEADER_STATUS_LOADING;
            setSelection(0);
        }
    }

    /**
     * adjust header padding according to motion event
     * 
     * @param ev
     */
    private void adjustHeaderPadding(MotionEvent ev) {
        // adjust header padding according to motion event history
        int pointerCount = ev.getHistorySize();
        if (isVerticalFadingEdgeEnabled()) {
            setVerticalScrollBarEnabled(false);
        }
        for (int i = 0; i < pointerCount; i++) {
            if (currentHeaderStatus == HEADER_STATUS_DROP_DOWN_TO_LOAD
                    || currentHeaderStatus == HEADER_STATUS_RELEASE_TO_LOAD) {
                headerLayout
                        .setPadding(
                                headerLayout.getPaddingLeft(),
                                (int)(((ev.getHistoricalY(i) - actionDownPointY) - headerOriginalHeight) / headerPaddingTopRate),
                                headerLayout.getPaddingRight(), headerLayout.getPaddingBottom());
            }
        }
    }

    /**
     * reset header padding
     */
    private void resetHeaderPadding() {
        headerLayout.setPadding(headerLayout.getPaddingLeft(), headerOriginalTopPadding,
                headerLayout.getPaddingRight(), headerLayout.getPaddingBottom());
    }

    /**
     * measure header layout
     * 
     * @param child
     */
    private void measureHeaderLayout(View child) {
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }

        int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width);
        int lpHeight = p.height;
        int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
    }

    /**
     * get attrs
     * 
     * @param context
     * @param attrs
     */
    private void getAttrs(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.drop_down_list_attr);
        isDropDownStyle = ta.getBoolean(R.styleable.drop_down_list_attr_isDropDownStyle, false);
        isOnBottomStyle = ta.getBoolean(R.styleable.drop_down_list_attr_isOnBottomStyle, false);
        isAutoLoadOnBottom = ta.getBoolean(R.styleable.drop_down_list_attr_isAutoLoadOnBottom, false);
        ta.recycle();
    }
}

其中的一些重要方法介绍:
public void setOnDropDownListener(OnDropDownListener onDropDownListener)设置下拉的事件
public void onDropDownComplete()和public void onDropDownComplete(CharSequence secondText)恢复下拉状态,注意onDropDownComplete需要在adapter.notifyDataSetChanged();后面调用

public void setOnBottomListener(OnClickListener onBottomListener)设置滚动到底部的事件
public void onBottomComplete()恢复底部状态

public void setSecondPositionVisible()
在isDropDownStyle为true情况下,drop down的header layout为ListView position为0的item,所以可能需要调用(如adapter.notifyDataSetChanged())setSecondPositionVisible()设置position为1(即第二个)的item可见。setSelection(0)滚动到的header layout的item。onDropDownComplete()默认已经调用setSecondPositionVisible()

public void setDropDownStyle(boolean isDropDownStyle)同xml中的isDropDownStyle属性,表示是否为下拉样式,下拉释放后执行onDropDownListener
public void setOnBottomStyle(boolean isOnBottomStyle)同xml中isOnBottomStyle属性,表示滚动到底部执行onBottomListener样式
public void setAutoLoadOnBottom(boolean isAutoLoadOnBottom)同xml中isAutoLoadOnBottom属性,表示滚动到底部是否自动执行onBottomListener

public void setHeaderPaddingTopRate(float headerPaddingTopRate)设置header padding top距离与实际下拉距离的比例
public void setHeaderReleaseMinDistance(int headerReleaseMinDistance)设置header可释放执行onDropDownListener的最小距离
public void setShowFooterProgressBar(boolean isShowFooterProgressBar)设置底部是否显示progressbar

public void setHasMore(boolean hasMore)set whether has more
public void setHeaderDefaultText(String headerDefaultText)设置header默认文字, default is R.string.drop_down_list_header_default_text
public void setHeaderPullText(String headerPullText)设置header下拉提示文字, default is R.string.drop_down_list_header_pull_text
public void setHeaderReleaseText(String headerReleaseText)设置header可释放提示文字, default is R.string.drop_down_list_header_release_text
public void setHeaderLoadingText(String headerLoadingText)设置header加载中提示文字, default is R.string.drop_down_list_header_loading_text
public void setFooterDefaultText(String footerDefaultText)设置footer默认文字, default is R.string.drop_down_list_footer_default_text
public void setFooterLoadingText(String footerLoadingText)设置footer加载中提示文字, default is R.string.drop_down_list_footer_loading_text
public void setFooterNoMoreText(String footerNoMoreText)设置footer没有更多提示文字, default is R.string.drop_down_list_footer_no_more_text
public void setHeaderSecondText(CharSequence secondText)设置header第二部分文字, default is null
本项目github地址:
https://github.com/CameloeAnthony/DropDownListViewDemo

推荐阅读更多精彩内容