Android 仿QQ 微信 即時(shí)通訊聊天listview和發(fā)送表情

效果圖1
效果圖2

仿照QQ和微信類似頁面聊天 發(fā)送表情頁面的demo.
MainActivity.java

package com.example.liupanpan.chatimg;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.example.liupanpan.chatimg.adapter.ChatLVAdapter;
import com.example.liupanpan.chatimg.adapter.FaceGVAdapter;
import com.example.liupanpan.chatimg.adapter.FaceVPAdapter;
import com.example.liupanpan.chatimg.bean.ChatInfo;
import com.example.liupanpan.chatimg.view.DropdownListView;
import com.example.liupanpan.chatimg.view.MyEditText;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * */
public class MainActivity extends Activity implements OnClickListener, DropdownListView.OnRefreshListenerHeader {
    private ViewPager mViewPager;
    private LinearLayout mDotsLayout;
    private MyEditText input;
    private Button send;
    private DropdownListView mListView;
    private ChatLVAdapter mLvAdapter;

    private LinearLayout chat_face_container;
    private ImageView image_face;//表情圖標(biāo)
    // 7列3行
    private int columns = 6;
    private int rows = 4;
    private List<View> views = new ArrayList<View>();
    private List<String> staticFacesList;
    private LinkedList<ChatInfo> infos = new LinkedList<ChatInfo>();
    private SimpleDateFormat sd;

    private String reply = "";//模擬回復(fù)

    @SuppressLint("SimpleDateFormat")
    private void initViews() {
        mListView = (DropdownListView) findViewById(R.id.message_chat_listview);
        sd = new SimpleDateFormat("MM-dd HH:mm");
        //模擬收到信息
        infos.add(getChatInfoFrom("你好暗忍浮庐!"));
        infos.add(getChatInfoFrom("認(rèn)識(shí)你很高興#[face/png/f_static_018.png]#"));
        mLvAdapter = new ChatLVAdapter(this, infos);
        mListView.setAdapter(mLvAdapter);
        //表情圖標(biāo)
        image_face = (ImageView) findViewById(R.id.image_face);
        //表情布局
        chat_face_container = (LinearLayout) findViewById(R.id.chat_face_container);
        mViewPager = (ViewPager) findViewById(R.id.face_viewpager);
        mViewPager.setOnPageChangeListener(new PageChange());
        //表情下小圓點(diǎn)
        mDotsLayout = (LinearLayout) findViewById(R.id.face_dots_container);
        input = (MyEditText) findViewById(R.id.input_sms);
        input.setOnClickListener(this);
        send = (Button) findViewById(R.id.send_sms);
        InitViewPager();
        //表情按鈕
        image_face.setOnClickListener(this);
        // 發(fā)送
        send.setOnClickListener(this);

        mListView.setOnRefreshListenerHead(this);
        mListView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View arg0, MotionEvent arg1) {
                if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
                    if (chat_face_container.getVisibility() == View.VISIBLE) {
                        chat_face_container.setVisibility(View.GONE);
                    }
                }
                return false;
            }
        });
    }


    @Override
    public void onClick(View arg0) {
        switch (arg0.getId()) {
            case R.id.input_sms://輸入框
                if (chat_face_container.getVisibility() == View.VISIBLE) {
                    chat_face_container.setVisibility(View.GONE);
                }
                break;
            case R.id.image_face://表情
                hideSoftInputView();//隱藏軟鍵盤
                if (chat_face_container.getVisibility() == View.GONE) {
                    chat_face_container.setVisibility(View.VISIBLE);
                } else {
                    chat_face_container.setVisibility(View.GONE);
                }
                break;
            case R.id.send_sms://發(fā)送
                reply = input.getText().toString();
                if (!TextUtils.isEmpty(reply)) {
                    infos.add(getChatInfoTo(reply));
                    mLvAdapter.setList(infos);
                    mLvAdapter.notifyDataSetChanged();
                    mListView.setSelection(infos.size() - 1);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            infos.add(getChatInfoFrom(reply));
                            mLvAdapter.setList(infos);
                            mLvAdapter.notifyDataSetChanged();
                            mListView.setSelection(infos.size() - 1);
                        }
                    }, 1000);
                    input.setText("");
                }
                break;
        }
    }

    /*
     * 初始表情 *
     */
    private void InitViewPager() {
        // 獲取頁數(shù)
        for (int i = 0; i < getPagerCount(); i++) {
            views.add(viewPagerItem(i));
            LayoutParams params = new LayoutParams(16, 16);
            mDotsLayout.addView(dotsItem(i), params);
        }
        FaceVPAdapter mVpAdapter = new FaceVPAdapter(views);
        mViewPager.setAdapter(mVpAdapter);
        mDotsLayout.getChildAt(0).setSelected(true);
    }

    private View viewPagerItem(int position) {
        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        View layout = inflater.inflate(R.layout.face_gridview, null);//表情布局
        GridView gridview = (GridView) layout.findViewById(R.id.chart_face_gv);
        /**
         * 注:因?yàn)槊恳豁撃┪捕加幸粋€(gè)刪除圖標(biāo)谜诫,所以每一頁的實(shí)際表情columns * rows√焊ā- 1; 空出最后一個(gè)位置給刪除圖標(biāo)
         * */
        List<String> subList = new ArrayList<String>();
        subList.addAll(staticFacesList
                .subList(position * (columns * rows - 1),
                        (columns * rows - 1) * (position + 1) > staticFacesList
                                .size() ? staticFacesList.size() : (columns
                                * rows - 1)
                                * (position + 1)));
        /**
         * 末尾添加刪除圖標(biāo)
         * */
        subList.add("emotion_del_normal.png");
        FaceGVAdapter mGvAdapter = new FaceGVAdapter(subList, this);
        gridview.setAdapter(mGvAdapter);
        gridview.setNumColumns(columns);
        // 單擊表情執(zhí)行的操作
        gridview.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                try {
                    String png = ((TextView) ((LinearLayout) view).getChildAt(1)).getText().toString();
                    if (!png.contains("emotion_del_normal")) {// 如果不是刪除圖標(biāo)
                        insert(getFace(png));
                    } else {
                        delete();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        return gridview;
    }

    private SpannableStringBuilder getFace(String png) {
        SpannableStringBuilder sb = new SpannableStringBuilder();
        try {
            /**
             * 經(jīng)過測(cè)試,雖然這里tempText被替換為png顯示稚疹,但是但我單擊發(fā)送按鈕時(shí)江滨,獲取到輸入框的內(nèi)容是tempText的值而不是png
             * 所以這里對(duì)這個(gè)tempText值做特殊處理
             * 格式:#[face/png/f_static_000.png]#菌赖,以方便判斷當(dāng)前圖片是哪一個(gè)
             * */
            String tempText = "#[" + png + "]#";
            sb.append(tempText);
            sb.setSpan(
                    new ImageSpan(MainActivity.this, BitmapFactory
                            .decodeStream(getAssets().open(png))), sb.length()
                            - tempText.length(), sb.length(),
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return sb;
    }

    /**
     * 向輸入框里添加表情
     */
    private void insert(CharSequence text) {
        int iCursorStart = Selection.getSelectionStart((input.getText()));
        int iCursorEnd = Selection.getSelectionEnd((input.getText()));
        if (iCursorStart != iCursorEnd) {
            ((Editable) input.getText()).replace(iCursorStart, iCursorEnd, "");
        }
        int iCursor = Selection.getSelectionEnd((input.getText()));
        ((Editable) input.getText()).insert(iCursor, text);
    }

    /**
     * 刪除圖標(biāo)執(zhí)行事件
     * 注:如果刪除的是表情司蔬,在刪除時(shí)實(shí)際刪除的是tempText即圖片占位的字符串邑茄,所以必需一次性刪除掉tempText肺缕,才能將圖片刪除
     */
    private void delete() {
        if (input.getText().length() != 0) {
            int iCursorEnd = Selection.getSelectionEnd(input.getText());
            int iCursorStart = Selection.getSelectionStart(input.getText());
            if (iCursorEnd > 0) {
                if (iCursorEnd == iCursorStart) {
                    if (isDeletePng(iCursorEnd)) {
                        String st = "#[face/png/f_static_000.png]#";
                        ((Editable) input.getText()).delete(
                                iCursorEnd - st.length(), iCursorEnd);
                    } else {
                        ((Editable) input.getText()).delete(iCursorEnd - 1,
                                iCursorEnd);
                    }
                } else {
                    ((Editable) input.getText()).delete(iCursorStart,
                            iCursorEnd);
                }
            }
        }
    }

    /**
     * 判斷即將刪除的字符串是否是圖片占位字符串tempText 如果是:則講刪除整個(gè)tempText
     **/
    private boolean isDeletePng(int cursor) {
        String st = "#[face/png/f_static_000.png]#";
        String content = input.getText().toString().substring(0, cursor);
        if (content.length() >= st.length()) {
            String checkStr = content.substring(content.length() - st.length(),
                    content.length());
            String regex = "(\\#\\[face/png/f_static_)\\d{3}(.png\\]\\#)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(checkStr);
            return m.matches();
        }
        return false;
    }

    private ImageView dotsItem(int position) {
        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        View layout = inflater.inflate(R.layout.dot_image, null);
        ImageView iv = (ImageView) layout.findViewById(R.id.face_dot);
        iv.setId(position);
        return iv;
    }

    /**
     * 根據(jù)表情數(shù)量以及GridView設(shè)置的行數(shù)和列數(shù)計(jì)算Pager數(shù)量
     *
     * @return
     */
    private int getPagerCount() {
        int count = staticFacesList.size();
        return count % (columns * rows - 1) == 0 ? count / (columns * rows - 1)
                : count / (columns * rows - 1) + 1;
    }

    /**
     * 初始化表情列表staticFacesList
     */
    private void initStaticFaces() {
        try {
            staticFacesList = new ArrayList<String>();
            String[] faces = getAssets().list("face/png");
            //將Assets中的表情名稱轉(zhuǎn)為字符串一一添加進(jìn)staticFacesList
            for (int i = 0; i < faces.length; i++) {
                staticFacesList.add(faces[i]);
            }
            //去掉刪除圖片
            staticFacesList.remove("emotion_del_normal.png");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 表情頁改變時(shí),dots效果也要跟著改變
     */
    class PageChange implements OnPageChangeListener {
        @Override
        public void onPageScrollStateChanged(int arg0) {
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageSelected(int arg0) {
            for (int i = 0; i < mDotsLayout.getChildCount(); i++) {
                mDotsLayout.getChildAt(i).setSelected(false);
            }
            mDotsLayout.getChildAt(arg0).setSelected(true);
        }

    }

    /**
     * 發(fā)送的信息
     *
     * @param message
     * @return
     */
    private ChatInfo getChatInfoTo(String message) {
        ChatInfo info = new ChatInfo();
        info.content = message;
        info.fromOrTo = 1;
        info.time = sd.format(new Date());
        return info;
    }

    /**
     * 接收的信息
     *
     * @param message
     * @return
     */
    private ChatInfo getChatInfoFrom(String message) {
        ChatInfo info = new ChatInfo();
        info.content = message;
        info.fromOrTo = 0;
        info.time = sd.format(new Date());
        return info;
    }

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    mLvAdapter.setList(infos);
                    mLvAdapter.notifyDataSetChanged();
                    mListView.onRefreshCompleteHeader();
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.chat_main);
        initStaticFaces();
        initViews();
    }

    @Override
    public void onRefresh() {
        new Thread() {
            @Override
            public void run() {
                try {
                    sleep(1000);
                    Message msg = mHandler.obtainMessage(0);
                    mHandler.sendMessage(msg);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    public void hideSoftInputView() {
        InputMethodManager manager = ((InputMethodManager) this.getSystemService(Activity.INPUT_METHOD_SERVICE));
        if (getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {
            if (getCurrentFocus() != null)
                manager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }

}

main.xml

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

    <include
        android:id="@+id/header"
        layout="@layout/header" />

    <com.example.liupanpan.chatimg.view.DropdownListView
        android:id="@+id/message_chat_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bottom"
        android:layout_below="@id/header"
        android:background="@color/white"
        android:cacheColorHint="@color/transparent"
        android:divider="@null"
        android:listSelector="@color/transparent" />

    <LinearLayout
        android:id="@+id/bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="vertical">

        <View
            android:layout_width="match_parent"
            android:layout_height="0.1dp"
            android:background="@color/gray" />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@drawable/chat_bottom_shape">

            <ImageView
                android:id="@+id/image_face"
                android:layout_width="30dip"
                android:layout_height="30dip"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:layout_marginLeft="4dip"
                android:src="@drawable/chat_emo_normal"
                android:visibility="visible" />

            <com.example.liupanpan.chatimg.view.MyEditText
                android:id="@+id/input_sms"
                android:layout_width="match_parent"
                android:layout_height="35dp"
                android:layout_centerVertical="true"
                android:layout_marginLeft="9dip"
                android:layout_marginRight="9dip"
                android:layout_toLeftOf="@+id/send_sms"
                android:layout_toRightOf="@id/image_face"
                android:background="@null"
                android:hint="@string/edittext_notice_0"
                android:padding="4dip"
                android:singleLine="true"
                android:textSize="14sp" />

            <Button
                android:id="@+id/send_sms"
                android:layout_width="50dp"
                android:layout_height="40dp"
                android:layout_alignBottom="@id/input_sms"
                android:layout_alignParentRight="true"
                android:layout_alignTop="@id/input_sms"
                android:layout_centerVertical="true"
                android:layout_marginLeft="5dip"
                android:layout_marginRight="5dip"
                android:background="@drawable/button_shape"
                android:gravity="center"
                android:text="@string/send"
                android:textColor="#578fbe"
                android:textSize="14sp" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.1dp"
            android:background="@color/gray" />

        <include
            android:id="@+id/chat_face_container"
            layout="@layout/chat_face_container"
            android:visibility="gone" />
    </LinearLayout>

</RelativeLayout>

自定義DropdownListView

package com.example.liupanpan.chatimg.view;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;

import com.example.liupanpan.chatimg.R;


public class DropdownListView extends ListView implements OnScrollListener {

    private static final String TAG = "listview";

    private final static int RELEASE_To_REFRESH = 0;
    private final static int PULL_To_REFRESH = 1;
    private final static int REFRESHING = 2;
    private final static int DONE = 3;
    private final static int LOADING = 4;

    // 實(shí)際的padding的距離與界面上偏移距離的比例
    private final static int RATIO = 3;

    private LayoutInflater inflater;
    private FrameLayout fl;
    private LinearLayout headView;

//  private View line;
    private ProgressBar progressBar;

//  private RotateAnimation animation;
//  private RotateAnimation reverseAnimation;

    // 用于保證startY的值在一個(gè)完整的touch事件中只被記錄一次
    private boolean isRecored;

    private int headContentWidth;
    private int headContentHeight;

    private int startY;
    private int firstItemIndex;

    private int state;

    private boolean isBack;

    private OnRefreshListenerHeader refreshListenerHeader;


    private boolean isRefreshableHeader;

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

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

    private void init(Context context) {
        setCacheColorHint(context.getResources().getColor(R.color.transparent));
        inflater = LayoutInflater.from(context);
        
        fl = (FrameLayout)inflater.inflate(R.layout.dropdown_lv_head, null);
        headView = (LinearLayout) fl.findViewById(R.id.drop_down_head);

        progressBar = (ProgressBar) fl.findViewById(R.id.loading);
        measureView(headView);
        
        
        headContentHeight = headView.getMeasuredHeight();
        headContentWidth = headView.getMeasuredWidth();

        headView.setPadding(0, -1 * headContentHeight, 0, 0);
        headView.invalidate();

        Log.v("size", "width:" + headContentWidth + " height:"
                + headContentHeight);

        addHeaderView(fl, null, false);
//      addHeaderView(headView, null, false);
        setOnScrollListener(this);


        state = DONE;
        isRefreshableHeader = false;
    }

    public void onScroll(AbsListView arg0, int firstVisiableItem, int arg2,
            int arg3) {
        firstItemIndex = firstVisiableItem;
    }

    public void onScrollStateChanged(AbsListView arg0, int scrollState) {
        if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
            // 判斷滾動(dòng)到底部

        }
    }

    public boolean onTouchEvent(MotionEvent event) {

        if (isRefreshableHeader) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (firstItemIndex == 0 && !isRecored) {
                    isRecored = true;
                    startY = (int) event.getY();
                    Log.v(TAG, "在down時(shí)候記錄當(dāng)前位置‘");
                }
                break;

            case MotionEvent.ACTION_UP:

                if (state != REFRESHING && state != LOADING) {
                    if (state == DONE) {
                        // 什么都不做
                    }
                    if (state == PULL_To_REFRESH) {
                        state = DONE;
                        changeHeaderViewByState();

                        Log.v(TAG, "由下拉刷新狀態(tài),到done狀態(tài)");
                    }
                    if (state == RELEASE_To_REFRESH) {
                        state = REFRESHING;
                        
                        changeHeaderViewByState();
//                      recoverLine();
                        onRefresh();
                        
                        Log.v(TAG, "由松開刷新狀態(tài)洲尊,到done狀態(tài)");
                    }
                }

                isRecored = false;
                isBack = false;

                break;

            case MotionEvent.ACTION_MOVE:
                int tempY = (int) event.getY();
                if (!isRecored && firstItemIndex == 0) {
                    Log.v(TAG, "在move時(shí)候記錄下位置");
                    isRecored = true;
                    startY = tempY;
                }

                if (state != REFRESHING && isRecored && state != LOADING) {

                    // 保證在設(shè)置padding的過程中坞嘀,當(dāng)前的位置一直是在head惊来,否則如果當(dāng)列表超出屏幕的話,當(dāng)在上推的時(shí)候内狸,列表會(huì)同時(shí)進(jìn)行滾動(dòng)

                    // 可以松手去刷新了
                    if (state == RELEASE_To_REFRESH) {

                        setSelection(0);

                        // 往上推了昆淡,推到了屏幕足夠掩蓋head的程度刽严,但是還沒有推到全部掩蓋的地步
                        if (((tempY - startY) / RATIO < headContentHeight)
                                && (tempY - startY) > 0) {
                            state = PULL_To_REFRESH;
                            changeHeaderViewByState();

                            Log.v(TAG, "由松開刷新狀態(tài)轉(zhuǎn)變到下拉刷新狀態(tài)");
                        }
                        // 一下子推到頂了
                        else if (tempY - startY <= 0) {
                            state = DONE;
                            changeHeaderViewByState();

                            Log.v(TAG, "由松開刷新狀態(tài)轉(zhuǎn)變到done狀態(tài)");
                        }
                        // 往下拉了舞萄,或者還沒有上推到屏幕頂部掩蓋head的地步
                        else {
                            // 不用進(jìn)行特別的操作,只用更新paddingTop的值就行了
                        }
                    }
                    // 還沒有到達(dá)顯示松開刷新的時(shí)候,DONE或者是PULL_To_REFRESH狀態(tài)
                    if (state == PULL_To_REFRESH) {

                        setSelection(0);

                        // 下拉到可以進(jìn)入RELEASE_TO_REFRESH的狀態(tài)
                        if ((tempY - startY) / RATIO >= headContentHeight) {
                            state = RELEASE_To_REFRESH;
                            isBack = true;
                            changeHeaderViewByState();

                            Log.v(TAG, "由done或者下拉刷新狀態(tài)轉(zhuǎn)變到松開刷新");
                        }
                        // 上推到頂了
                        else if (tempY - startY <= 0) {
                            state = DONE;
                            changeHeaderViewByState();

                            Log.v(TAG, "由DOne或者下拉刷新狀態(tài)轉(zhuǎn)變到done狀態(tài)");
                        }
                    }

                    // done狀態(tài)下
                    if (state == DONE) {
                        if (tempY - startY > 0) {
                            state = PULL_To_REFRESH;
                            changeHeaderViewByState();
                        }
                    }

                    // 更新headView的size
                    if (state == PULL_To_REFRESH) {
                        headView.setPadding(0, -1 * headContentHeight
                                + (tempY - startY) / RATIO, 0, 0);
                    }

                    // 更新headView的paddingTop
                    if (state == RELEASE_To_REFRESH) {
                        headView.setPadding(0, (tempY - startY) / RATIO
                                - headContentHeight, 0, 0);
                    }
                }

                break;
            }
        }

        return super.onTouchEvent(event);
    }

    // 當(dāng)狀態(tài)改變時(shí)候,調(diào)用該方法甘晤,以更新界面
    private void changeHeaderViewByState() {
        switch (state) {
        case RELEASE_To_REFRESH:
            progressBar.setVisibility(View.VISIBLE);

            Log.v(TAG, "當(dāng)前狀態(tài),松開刷新");
            break;
        case PULL_To_REFRESH:
            progressBar.setVisibility(View.VISIBLE);
            // 是由RELEASE_To_REFRESH狀態(tài)轉(zhuǎn)變來的
            if (isBack) {
                isBack = false;

            } else {
            }
            Log.v(TAG, "當(dāng)前狀態(tài)遏弱,下拉刷新");
            break;

        case REFRESHING:

            headView.setPadding(0, 0, 0, 0);

            progressBar.setVisibility(View.VISIBLE);

            Log.v(TAG, "當(dāng)前狀態(tài),正在刷新...");
            break;
        case DONE:
            headView.setPadding(0, -1 * headContentHeight, 0, 0);

            progressBar.setVisibility(View.GONE);
            Log.v(TAG, "當(dāng)前狀態(tài)塞弊,done");
            break;
        }
    }

    public void setOnRefreshListenerHead(
            OnRefreshListenerHeader refreshListenerHeader) {
        this.refreshListenerHeader = refreshListenerHeader;
        isRefreshableHeader = true;
    }


    public interface OnRefreshListenerHeader {
        public void onRefresh();
    }

    public interface OnRefreshListenerFooter {
        public void onRefresh();
    }

    public void onRefreshCompleteHeader() {
        state = DONE;
        changeHeaderViewByState();
    }

    private void onRefresh() {
        if (refreshListenerHeader != null) {
            refreshListenerHeader.onRefresh();
        }
    }

    // 此方法直接照搬自網(wǎng)絡(luò)上的一個(gè)下拉刷新的demo游沿,此處是“估計(jì)”headView的width以及height
    private void measureView(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 + 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);
    }

    public void setAdapter(BaseAdapter adapter) {
        super.setAdapter(adapter);
    }
}

自定義MyEditText

package com.example.liupanpan.chatimg.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.Toast;

/**
 * An EditText, which notifies when something was cut/copied/pasted inside it.
 * 
 * @author Lukas Knuth
 * @version 1.0
 */
@SuppressLint("NewApi") public class MyEditText extends EditText implements
        MenuItem.OnMenuItemClickListener {
    private static final int ID_SELECTION_MODE = android.R.id.selectTextMode;
      // Selection context mode
    private static final int ID_SELECT_ALL = android.R.id.selectAll;
    private static final int ID_CUT = android.R.id.cut;
    private static final int ID_COPY = android.R.id.copy;
    private static final int ID_PASTE = android.R.id.paste;
    
    private final Context mContext;

    /*
     * Just the constructors to create a new EditText...
     */
    public MyEditText(Context context) {
        super(context);
        this.mContext = context;
    }

    public MyEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
    }

    public MyEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mContext = context;
    }

    @Override
    protected void onCreateContextMenu(ContextMenu menu) {
//      menu.add(0, ID_PASTE, 0, "粘貼").setOnMenuItemClickListener(this);
//      menu.add(0, ID_CUT, 1, "剪切").setOnMenuItemClickListener(this);
//      menu.add(0, ID_COPY, 1, "復(fù)制").setOnMenuItemClickListener(this);
//      menu.add(0, ID_SELECT_ALL, 1, "全選").setOnMenuItemClickListener(this);
        super.onCreateContextMenu(menu);
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        // TODO Auto-generated method stub
        return onTextContextMenuItem(item.getItemId());
    }

    @Override
    public boolean onTextContextMenuItem(int id) {
        // Do your thing:
        boolean consumed = super.onTextContextMenuItem(id);
        // React:
        switch (id) {
        case android.R.id.cut:
            onTextCut();
            break;
        case android.R.id.paste:
            onTextPaste();
            break;
        case android.R.id.copy:
            onTextCopy();
        }
        return consumed;
    }

    /**
     * Text was cut from this EditText.
     */
    public void onTextCut() {
        Toast.makeText(mContext, "Cut!", Toast.LENGTH_SHORT).show();
    }

    /**
     * Text was copied from this EditText.
     */
    public void onTextCopy() {
        Toast.makeText(mContext, "Copy!", Toast.LENGTH_SHORT).show();
    }

    /**
     * Text was pasted into the EditText.
     */
    public void onTextPaste() {
        Toast.makeText(mContext, "Paste!", Toast.LENGTH_SHORT).show();
    }
}

最后附上 源碼 關(guān)注我的公眾號(hào) 回復(fù):(QQ發(fā)送表情)即可得到demo鏈接

圖片.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咒彤,隨后出現(xiàn)的幾起案子镶柱,更是在濱河造成了極大的恐慌模叙,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件故觅,死亡現(xiàn)場離奇詭異输吏,居然都是意外死亡替蛉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門它浅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姐霍,“玉大人,你說我怎么就攤上這事黔衡‰缦纾” “怎么了夜牡?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵塘装,是天一觀的道長蹦肴。 經(jīng)常有香客問我,道長勺阐,這世上最難降的妖魔是什么矛双? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任议忽,我火速辦了婚禮,結(jié)果婚禮上愤估,老公的妹妹穿的比我還像新娘速址。我一直安慰自己,他們只是感情好震捣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布蒿赢。 她就那樣靜靜地躺著渣触,像睡著了一般。 火紅的嫁衣襯著肌膚如雪皂冰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天赂蕴,我揣著相機(jī)與錄音概说,去河邊找鬼嚣伐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛放典,可吹牛的內(nèi)容都是我干的基茵。 我是一名探鬼主播拱层,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼醋火!你這毒婦竟也來了芥驳?” 一聲冷哼從身側(cè)響起茬高,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤怎栽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后脚祟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體强饮,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年铭乾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了娃循。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捌斧。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出洞难,到底是詐尸還是另有隱情揭朝,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布柱嫌,位于F島的核電站编丘,受9級(jí)特大地震影響彤悔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抑片,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一敞斋、第九天 我趴在偏房一處隱蔽的房頂上張望疾牲。 院中可真熱鬧,春花似錦鸥跟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽很泊。三九已至,卻和暖如春戳鹅,著一層夾襖步出監(jiān)牢的瞬間昏兆,已是汗流浹背爬虱。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留死讹,地道東北人曲梗。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓虏两,卻偏偏與公主長得像碘举,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子引颈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,734評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫凌停、插件售滤、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,058評(píng)論 4 62
  • 匆匆那年 不敢聽這首歌,會(huì)潸然淚下拉队,不是在臉上阻逮,是燙在心上。 曾經(jīng)以為早已忘懷的一切事哭,會(huì)隨著旋律瓜富,一幕幕回放?? ...
    暗香盈夢(mèng)閱讀 560評(píng)論 0 3