android 仿微信demo————微信通讯录界面功能实现(移动端,服务端)

android 仿微信demo————微信启动界面实现

android 仿微信demo————注册功能实现(移动端)

android 仿微信demo————注册功能实现(服务端)

android 仿微信demo————登录功能实现(移动端)

android 仿微信demo————登录功能实现(服务端)

android 仿微信demo————微信主界面实现

android 仿微信demo————微信消息界面实现(移动端)

android 仿微信demo————微信消息界面实现(服务端)

android 仿微信demo————微信通讯录界面功能实现(移动端,服务端)

android 仿微信demo————微信发现界面实现

android 仿微信demo————微信顶部操作栏界面实现

android 仿微信demo————微信顶部操作栏搜索按钮实现(查询通讯录好友功能)

android 仿微信demo————微信顶部操作栏加号按钮实现(弹出子菜单)

前面我们实现了微信消息界面的实现,这篇继续完善微信功能,实现微信通讯录界面

移动端微信通讯录界面功能实现

微信通讯录,头部是四个标签(不进行分组),下面是好友信息且根据呢称首字母进行排序分组,底部还统计了好友个数,右边是一组英文字母导航,可滑动并且还可以点击跳转到相应的分组
在这里插入图片描述
微信好友和顶部的四个标签,可以用ListViw实现并指定一个item布局,分组效果只需要在代码段进行判断即可

右边的字母操作行可以自定义一个组件继承AppCompatTextView,为什么要用它呢,而不用TextView呢?因为UI设计限定了一个文本的宽度,但是文本的长度可能比较长,如果设定一个固定的textSize,就导致一部分文本无法显示,而AppCompatTextView最显著的特点是可以自适应字体宽度大小变化。这个特点很有用,可以让文本随着文本宽度的变化,限定在一个固定范围内完整显示出来:

修改微信通信录界面的fragment布局
contactlist_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.wxchatdemo.MainWeixin">
    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@drawable/main_list_divider_line"
        android:dividerHeight="1.5px"
        android:layout_marginBottom="50dp" />
    <com.example.wxchatdemo.tools.SideBar
        android:id="@+id/side_bar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="100dp"
        android:layout_marginTop="100dp"
        android:paddingRight="10dp"
        android:textColor="@color/black"
        android:textSize="9sp" />
</RelativeLayout>

fragment整体布局使用相对布局,这样可以通过 android:layout_alignParentRight="true"属性指定右边的自定义字母导航(SideBar继承AppCompatTextView)在父容器右边(即在屏幕中间的右边),相对布局包括两个组件(ListView,SideBar)

创建自定义组件SideBar.java继承AppCompatTextView
SideBar.java

package com.example.wxchatdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;

public class SideBar extends android.support.v7.widget.AppCompatTextView {
    private String[] letters = new String[]{"A", "B", "C", "D", "E", "F",
            "G", "H", "I", "J", "K", "L",
            "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
            "W", "X", "Y", "Z", "#"};
    private Paint textPaint;
    private Paint bigTextPaint;
    private Paint scaleTextPaint;
    private Canvas canvas;
    private int itemH;
    private int w;
    private int h;
    /**
     * 普通情况下字体大小
     */
    float singleTextH;
    /**
     * 缩放离原始的宽度
     */
    private float scaleWidth;
    /**
     * 滑动的Y
     */
    private float eventY = 0;
    /**
     * 缩放的倍数
     */
    private int scaleSize = 1;
    /**
     * 缩放个数item,即开口大小
     */
    private int scaleItemCount = 6;
    private ISideBarSelectCallBack callBack;

    public SideBar(Context context) {
        this(context, null);
    }

    public SideBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SideBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        if (attrs != null) {
            TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SideBar);
            scaleSize = ta.getInteger(R.styleable.SideBar_scaleSize, 1);
            scaleItemCount = ta.getInteger(R.styleable.SideBar_scaleItemCount, 6);
            scaleWidth = ta.getDimensionPixelSize(R.styleable.SideBar_scaleWidth, dp(100));
            ta.recycle();
        }
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(getCurrentTextColor());
        textPaint.setTextSize(getTextSize());
        textPaint.setTextAlign(Paint.Align.CENTER);
        bigTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bigTextPaint.setColor(getCurrentTextColor());
        bigTextPaint.setTextSize(getTextSize() * (scaleSize + 3));
        bigTextPaint.setTextAlign(Paint.Align.CENTER);
        scaleTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scaleTextPaint.setColor(getCurrentTextColor());
        scaleTextPaint.setTextSize(getTextSize() * (scaleSize + 1));
        scaleTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    public void setDataResource(String[] data) {
        letters = data;
        invalidate();
    }

    public void setOnStrSelectCallBack(ISideBarSelectCallBack callBack) {
        this.callBack = callBack;
    }

    /**
     * 设置字体缩放比例     *     * @param scale
     */
    public void setScaleSize(int scale) {
        scaleSize = scale;
        invalidate();
    }

    /**
     * 设置缩放字体的个数,即开口大小     *     * @param scaleItemCount
     */
    public void setScaleItemCount(int scaleItemCount) {
        this.scaleItemCount = scaleItemCount;
        invalidate();
    }

    private int dp(int px) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (px * scale + 0.5f);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {
                    eventY = event.getY();
                    invalidate();
                    return true;
                } else {
                    eventY = 0;
                    invalidate();
                    break;
                }
            case MotionEvent.ACTION_CANCEL:
                eventY = 0;
                invalidate();
                return true;
            case MotionEvent.ACTION_UP:
                if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {
                    eventY = 0;
                    invalidate();
                    return true;
                } else
                    break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        this.canvas = canvas;
        DrawView(eventY);
    }

    private void DrawView(float y) {
        int currentSelectIndex = -1;
        if (y != 0) {
            for (int i = 0; i < letters.length; i++) {
                float currentItemY = itemH * i;
                float nextItemY = itemH * (i + 1);
                if (y >= currentItemY && y < nextItemY) {
                    currentSelectIndex = i;
                    if (callBack != null) {
                        callBack.onSelectStr(currentSelectIndex, letters[i]);
                    }                    //画大的字母
                    Paint.FontMetrics fontMetrics = bigTextPaint.getFontMetrics();
                    float bigTextSize = fontMetrics.descent - fontMetrics.ascent;
                    canvas.drawText(letters[i], w - getPaddingRight() - scaleWidth - bigTextSize,
                            singleTextH + itemH * i, bigTextPaint);
                }
            }
        }
        drawLetters(y, currentSelectIndex);
    }

    private void drawLetters(float y, int index) {        //第一次进来没有缩放情况,默认画原图
        if (index == -1) {
            w = getMeasuredWidth();
            h = getMeasuredHeight();
            itemH = h / letters.length;
            Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
            singleTextH = fontMetrics.descent - fontMetrics.ascent;
            for (int i = 0; i < letters.length; i++) {
                canvas.drawText(letters[i], w - getPaddingRight(), singleTextH + itemH * i, textPaint);
            }            //触摸的时候画缩放图
        } else {            //遍历所有字母
            for (int i = 0; i < letters.length; i++) {                //要画的字母的起始Y坐标
                float currentItemToDrawY = singleTextH + itemH * i;
                float centerItemToDrawY;
                if (index < i)
                    centerItemToDrawY = singleTextH + itemH * (index + scaleItemCount);
                else
                    centerItemToDrawY = singleTextH + itemH * (index - scaleItemCount);
                float delta = 1 - Math.abs((y - currentItemToDrawY) / (centerItemToDrawY - currentItemToDrawY));
                float maxRightX = w - getPaddingRight();                //如果大于0,表明在y坐标上方
                scaleTextPaint.setTextSize(getTextSize() + getTextSize() * delta);
                float drawX = maxRightX - scaleWidth * delta;                //超出边界直接花在边界上
                if (drawX > maxRightX)
                    canvas.drawText(letters[i], maxRightX, singleTextH + itemH * i, textPaint);
                else
                    canvas.drawText(letters[i], drawX, singleTextH + itemH * i, scaleTextPaint);
            }
        }
    }

    public interface ISideBarSelectCallBack {
        void onSelectStr(int index, String selectStr);
    }
}

右侧字母导航条,包括3个自定义的属性,下面将给出

在colors.xml文件添加如下代码
colors.xml

    <declare-styleable name="SideBar">
        <attr name="scaleSize" format="integer"/>
        <attr name="scaleItemCount" format="integer"/>
        <attr name="scaleWidth" format="dimension"/>
    </declare-styleable>

要在字母导航中点击每一个字母可跳转相应分组,需要借助汉字转拼音工具类和自定义字母排序类,我们知道,java中是没有提供接口和方法让我们直接将汉字转成拼音的。在此我选择了使用第三方jar包的方式,因为它体积不大而且更加准确。

创建汉字转拼音工具类Cn2Spell.java
Cn2Spell.java

package com.example.wxchatdemo.tools;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;

/** * 汉字转换位汉语拼音,英文字符不变 */
public class Cn2Spell {
    public static StringBuffer sb = new StringBuffer();
    /**     * 获取汉字字符串的首字母,英文字符不变     * 例如:阿飞→af     */
    public static String getPinYinHeadChar(String chines) {
        sb.setLength(0);
        char[] chars = chines.toCharArray();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] > 128) {
                try {
                    sb.append(PinyinHelper.toHanyuPinyinStringArray(chars[i], defaultFormat)[0].charAt(0));                }
                catch (Exception e) {
                    e.printStackTrace();                }
            }
            else {
                sb.append(chars[i]);            }
        }
        return sb.toString();    }
        /**     * 获取汉字字符串的第一个字母     */
        public static String getPinYinFirstLetter(String str) {
            sb.setLength(0);
            char c = str.charAt(0);
            String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);
            if (pinyinArray != null) {
                sb.append(pinyinArray[0].charAt(0));        }
            else {
                sb.append(c);
            }
            return sb.toString();
        }
        /**     * 获取汉字字符串的汉语拼音,英文字符不变     */
        public static String getPinYin(String chines) {
            sb.setLength(0);
            char[] nameChar = chines.toCharArray();
            HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
            defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
            defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
            for (int i = 0; i < nameChar.length; i++) {
                if (nameChar[i] > 128) {
                    try {
                        sb.append(PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0]);}
                    catch (Exception e) {
                    e.printStackTrace();                }
                }
                else {
                    sb.append(nameChar[i]);            }
            }
            return sb.toString();
        }
}

上面用到第三方jar,下面将给出下载地址

第三方jar包下载地址:https://pan.baidu.com/s/1R27rd4PxASgFsjFjvDcu0Q(提取吗:vcjq)

把jar包导入项目libs库中,方法如下

在android studio中把项目结构改成project
在这里插入图片描述

此时目录就可以看到lib目录,把jar复制进去,然后添加到库中,方法和idea的是一样的,在以往文章中已经演示了
在这里插入图片描述

创建自定义字母排序类User.java
User.java

package com.example.wxchatdemo.tools;

 public class User implements Comparable<User> {
     private String name; // 姓名
     private String pinyin; // 姓名对应的拼音
     private String firstLetter; // 拼音的首字母
     public User() {    }
     public User(String name) {
         this.name = name;
         pinyin = Cn2Spell.getPinYin(name); // 根据姓名获取拼音
         firstLetter = pinyin.substring(0, 1).toUpperCase(); // 获取拼音首字母并转成大写
         if (!firstLetter.matches("[A-Z]")) { // 如果不在A-Z中则默认为“#”
        firstLetter = "#";        }
     }
     public String getName() {
         return name;
     }
     public String getPinyin() {
         return pinyin;
     }
     public String getFirstLetter() {
         return firstLetter;
     }
     @Override
     public int compareTo(com.example.wxchatdemo.tools.User another) {
         if (firstLetter.equals("#") && !another.getFirstLetter().equals("#")) {
             return 1;
         }
         else if (!firstLetter.equals("#") && another.getFirstLetter().equals("#")){
             return -1;
         } else {
             return pinyin.compareToIgnoreCase(another.getPinyin());
         }
     }
 }

选择实现comparable接口,并重写comparaTo方法,原理很简单,就是先根据首字母判断,首字母为“#”都放在最后,都为“#”或者都是字母时才根据拼音来比较排序,让你的好友可以根据拼音来排序

修改通讯录fragment.java代码
ContactListFragment.java

package com.example.wxchatdemo;

import android.annotation.SuppressLint;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;

import com.example.wxchatdemo.adapter.SortAdapter;
import com.example.wxchatdemo.tools.User;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@SuppressLint("ValidFragment")
public class ContactListFragment extends Fragment {
    String[] imgUrl;
    String[] name;
    private String number; //微信号,通过微信号去查找通讯录
    /* 声明组件*/
    private ListView listView;
    private SideBar sideBar;
    /*声明或创建集合,用于处理数据*/
    private ArrayList<User> list;
    private ArrayList<Integer> list2;
    private List<Map<String, String>> data = new ArrayList<Map<String, String>>();
    //自定义的一个Hander消息机制
    private MyHander myhander = new MyHander();

    /*有参构造方法,参数为微信号*/
    @SuppressLint("ValidFragment")
    ContactListFragment(String number) {
        this.number = number;
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        /*开启一个线程,用微信号向服务器请求通讯录数据*/
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                httpUrlConnPost(String.valueOf(number));
            }
        });
        thread1.start();
        /*等待线性处理完成*/
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //获取fragment布局
        View view = inflater.inflate(R.layout.contactlist_fragment, container, false);
        /*初始化组件*/
        listView = (ListView) view.findViewById(R.id.listView);
        sideBar = (SideBar) view.findViewById(R.id.side_bar);
        //初始化数据
        initData();
        sideBar.setOnStrSelectCallBack(new SideBar.ISideBarSelectCallBack() {
            @Override
            public void onSelectStr(int index, String selectStr) {
                for (int i = 0; i < list.size(); i++) {
                    if (list.get(i).getName() == "新的朋友" || list.get(i).getName() == "群聊" ||
                            list.get(i).getName() == "标签" || list.get(i).getName() == "公众号"  )
                        continue;
                    if (selectStr.equalsIgnoreCase(list.get(i).getFirstLetter())) {
                        listView.setSelection(i); // 选择到首字母出现的位置
                        return;
                    }
                }
            }
        });
        return view;
    }

    private void initData() {
        //把从服务器获取解析的数据添加到map中,方便处理
        Map<String, String> map = new HashMap<String, String>();
        for (int i = 0; i < imgUrl.length; i ++) {
            map.put(name[i], imgUrl[i]);
        }
        data.add(map);
        //名字要提取出来在添加到list中,因为要进行字母排序
        list = new ArrayList<>();
        for (int i = 0; i < imgUrl.length; i++) {
            list.add(new User(name[i]));
        }
        Collections.sort(list); // 对list进行排序,需要让User实现Comparable接口重写compareTo方法
        //四个标签排序后再进行添加,好进行条件判断分离出来
        list.add(0,new User("新的朋友"));
        list.add(1,new User("群聊"));
        list.add(2,new User("标签"));
        list.add(3,new User("公众号"));
        //四个标签图片不需要再服务器获取,直接移动端实现即可
        list2 = new ArrayList<>();
        list2.add(R.drawable.newfriend);
        list2.add(R.drawable.groupchat);
        list2.add(R.drawable.sign);
        list2.add(R.drawable.publicnum);
        /*创建自定义适配器,并设置给listview*/
        SortAdapter adapter = new SortAdapter(getActivity().getApplicationContext(), list, list2, data);
        listView.setAdapter(adapter);
    }

    // 1.编写一个发送请求的方法
    // 发送请求的主要方法
    public void httpUrlConnPost(String number) {
        HttpURLConnection urlConnection = null;
        URL url;
        try {
            // 请求的URL地地址
            url = new URL(
                    "http://100.2.178.10:8080/AndroidServer_war_exploded/Contact");
            urlConnection = (HttpURLConnection) url.openConnection();// 打开http连接
            urlConnection.setConnectTimeout(3000);// 连接的超时时间
            urlConnection.setUseCaches(false);// 不使用缓存
            // urlConnection.setFollowRedirects(false);是static函数,作用于所有的URLConnection对象。
            urlConnection.setInstanceFollowRedirects(true);// 是成员函数,仅作用于当前函数,设置这个连接是否可以被重定向
            urlConnection.setReadTimeout(3000);// 响应的超时时间
            urlConnection.setDoInput(true);// 设置这个连接是否可以写入数据
            urlConnection.setDoOutput(true);// 设置这个连接是否可以输出数据
            urlConnection.setRequestMethod("POST");// 设置请求的方式
            urlConnection.setRequestProperty("Content-Type",
                    "application/json;charset=UTF-8");// 设置消息的类型
            urlConnection.connect();// 连接,从上述至此的配置必须要在connect之前完成,实际上它只是建立了一个与服务器的TCP连接
            JSONObject json = new JSONObject();// 创建json对象
            //json.put("title", URLEncoder.encode(title, "UTF-8"));// 使用URLEncoder.encode对特殊和不可见字符进行编码
            json.put("number", URLEncoder.encode(number, "UTF-8"));// 把数据put进json对象中
            String jsonstr = json.toString();// 把JSON对象按JSON的编码格式转换为字符串
            // ------------字符流写入数据------------
            OutputStream out = urlConnection.getOutputStream();// 输出流,用来发送请求,http请求实际上直到这个函数里面才正式发送出去
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));// 创建字符流对象并用高效缓冲流包装它,便获得最高的效率,发送的是字符串推荐用字符流,其它数据就用字节流
            bw.write(jsonstr);// 把json字符串写入缓冲区中
            bw.flush();// 刷新缓冲区,把数据发送出去,这步很重要
            out.close();
            bw.close();// 使用完关闭
            Log.i("aa", urlConnection.getResponseCode()+"");
            //以下判斷是否訪問成功,如果返回的状态码是200则说明访问成功
            if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {// 得到服务端的返回码是否连接成功
                // ------------字符流读取服务端返回的数据------------
                InputStream in = urlConnection.getInputStream();
                BufferedReader br = new BufferedReader(
                        new InputStreamReader(in));
                String str = null;
                StringBuffer buffer = new StringBuffer();
                while ((str = br.readLine()) != null) {// BufferedReader特有功能,一次读取一行数据
                    System.out.println("测试:" + str);
                    buffer.append(str);
                }
                in.close();
                br.close();
                JSONObject rjson = new JSONObject(buffer.toString());
                String str1 = rjson.getJSONObject("json").get("img").toString();
                imgUrl = str1.split("\r\n");
                String str2 = rjson.getJSONObject("json").get("name").toString();
                name = str2.split("\r\n");
                boolean result = rjson.getBoolean("json");// 从rjson对象中得到key值为"json"的数据,这里服务端返回的是一个boolean类型的数据
                System.out.println("json:===" + result);
                //如果服务器端返回的是true,则说明注册成功,否则注册失败
                if (result) {// 判断结果是否正确
                    //在Android中http请求,必须放到线程中去作请求,但是在线程中不可以直接修改UI,只能通过hander机制来完成对UI的操作
                    myhander.sendEmptyMessage(1);
                    Log.i("用户:", "登录成功");
                } else {
                    myhander.sendEmptyMessage(2);
                    System.out.println("222222222222222");
                    Log.i("用户:", "登录失败");
                }
            } else {
                myhander.sendEmptyMessage(2);
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.i("aa", e.toString());
            System.out.println("11111111111111111");
            myhander.sendEmptyMessage(2);
        } finally {
            urlConnection.disconnect();// 使用完关闭TCP连接,释放资源
        }
    }

    // 在Android中不可以在线程中直接修改UI,只能借助Handler机制来完成对UI的操作
    class MyHander extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //判断hander的内容是什么,如果是1则说明注册成功,如果是2说明注册失败
            switch (msg.what) {
                case 1:
                    Log.i("aa", msg.what + "");
                    break;
                case 2:
                    Log.i("aa", msg.what + "");
            }
        }
    }
}

上面代码主要内容就是先向服务器发送请求获取微信好友信息,初始化UI和数据,把数据封装再集合里并且实现滑动或选择字母索引时的回调接口,然后通过给ListView设置自定义的适配器(后面会给出),并把数据集合一并传过去

既然用到了ListView,我们就还需要一个适配器。

创建自定义适配器SortAdapter.java
SortAdapter.java

package com.example.wxchatdemo.adapter;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.wxchatdemo.R;
import com.example.wxchatdemo.tools.User;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;

public class SortAdapter extends BaseAdapter{

    //自定义handler机制
    private ImageHandler imgHandler = new ImageHandler();
    private Bitmap img;
    private ViewHolder viewHolder;
    private List<User> list = null;
    private List<Integer> list2 = null;
    private List<Map<String, String>> data = null;
    private Context mContext;

    public SortAdapter(Context mContext, List<User> list, List<Integer> list2, List<Map<String, String>> data) {
        this.mContext = mContext;
        this.list = list;
        this.list2 = list2;
        this.data = data;
    }

    public int getCount() {
        return this.list.size();
    }

    public Object getItem(int position) {
        return list.get(position);
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(final int position, View view, ViewGroup arg2) {

        final User user = list.get(position);
        if (view == null) {
            viewHolder = new ViewHolder();
            //获取listview对应的item布局
            view = LayoutInflater.from(mContext).inflate(R.layout.contactlist_item, null);
            //初始化组件
            viewHolder.img = (ImageView) view.findViewById(R.id.img);
            viewHolder.name = (TextView) view.findViewById(R.id.name);
            viewHolder.catalog = (TextView) view.findViewById(R.id.catalog);
            viewHolder.contact_count = (TextView) view.findViewById(R.id.contact_count);
            viewHolder.divider = (View) view.findViewById(R.id.divider);
            view.setTag(viewHolder);
        } else {
            System.out.println("position=" + position);
            viewHolder = (ViewHolder) view.getTag();
        }
        //0~3是标签单独处理
        if (position == 0 || position == 1 || position == 2 || position == 3) {
            viewHolder.catalog.setVisibility(View.GONE);
            viewHolder.img.setImageResource(list2.get(position));
            viewHolder.name.setText(this.list.get(position).getName());
            viewHolder.divider.setVisibility(View.GONE);
            viewHolder.contact_count.setVisibility(View.GONE);
        }else  {
                //根据position获取首字母作为目录catalog
                String catalog = list.get(position).getFirstLetter();
                //如果当前位置等于该分类首字母的Char的位置 ,则认为是第一次出现
                if(position == getPositionForSection(catalog)){
                    viewHolder.catalog.setVisibility(View.VISIBLE);
                    viewHolder.catalog.setText(user.getFirstLetter().toUpperCase());
                }else{
                    viewHolder.catalog.setVisibility(View.GONE);
                }
                Map<String, String> map = data.get(0);
                Thread thread1 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        img =  getImg(map.get(list.get(position).getName()));
                        Message msg = imgHandler.obtainMessage();
                        msg.what = 0;
                        msg.obj = img;
                        imgHandler.sendMessage(msg);
                    }
                });
                thread1.start();
                try {
                    thread1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                viewHolder.img.setImageBitmap(img);
                viewHolder.name.setText(this.list.get(position).getName());
                if (position == list.size() - 1) {
                    viewHolder.divider.setVisibility(View.VISIBLE);
                    viewHolder.contact_count.setVisibility(View.VISIBLE);
                    viewHolder.contact_count.setText(Integer.toString(position - 4) + "个朋友");
                }else {
                    viewHolder.divider.setVisibility(View.GONE);
                    viewHolder.contact_count.setVisibility(View.GONE);
                }
        }
        return view;
    }

    final static class ViewHolder {
        TextView catalog;
        ImageView img;
        TextView name;
        TextView contact_count;
        View divider;
    }

    /**
     * 获取catalog首次出现位置
     */
    public int getPositionForSection(String catalog) {
        for (int i = 0; i < getCount(); i++) {
            if (list.get(i).getName() != "新的朋友" && list.get(i).getName() != "群聊" &&
            list.get(i).getName() != "标签" && list.get(i).getName() != "公众号") {
                String sortStr = list.get(i).getFirstLetter();
                if (catalog.equalsIgnoreCase(sortStr)) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 从服务器读取图片流数据,并转换为Bitmap格式
     * @return Bitmap
     */
    private Bitmap getImg(String url){
        Bitmap img = null;

        try {
            URL imgUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) imgUrl.openConnection();
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(1000 * 6);
            conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("Charset", "UTF-8");
            conn.connect();
            //输出流写参数
            DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
            String param = getParam();
            dos.writeBytes(param);
            dos.flush();
            dos.close();
            int resultCode = conn.getResponseCode();
            if(HttpURLConnection.HTTP_OK == resultCode){
                InputStream is = conn.getInputStream();
                img = BitmapFactory.decodeStream(is);
                is.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return img;
    }

    /**
     * 测试参数
     * @return
     */
    private String getParam(){
        JSONObject jsObj = new JSONObject();
        try {
            jsObj.put("picFormat", "jpg");
            jsObj.put("testParam", "9527");
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return jsObj.toString();
    }

    /**
     * 异步线程请求到的图片数据,利用Handler,在主线程中显示
     */
    class ImageHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case 0:
                    img = (Bitmap)msg.obj;
                    break;
                default:
                    break;
            }
        }
    }
}

上面代码主要功能就是把数据呈现再相应组件上

适配器还用到了一个布局,即listview对应布局,创建contactlist_item.xml
contactlist_item.xml

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

    <TextView
        android:id="@+id/catalog"
        android:layout_width="match_parent"
        android:layout_height="32dp"
        android:background="#E0E0E0"
        android:paddingLeft="10dp"
        android:paddingTop="5dp"
        android:textColor="#454545"
        android:textSize="13sp" />
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/img"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:padding="10dp" />

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:padding="10dp"
            android:textColor="#336598"
            android:textSize="16sp" />
    </LinearLayout>

    <View
        android:id="@+id/divider"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#90909090" />

    <TextView
        android:id="@+id/contact_count"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#FFFFFF"
        android:paddingLeft="150dp"
        android:paddingTop="5dp"
        android:textColor="#454545"
        android:textSize="13sp" />
    
</LinearLayout>

布局有四部分,第一个是目录,即A,B,C,D这样的索引,仅当该目录下的第一项出现时才显示;第二个是线性布局,里面包括微信头像和姓名,第三个是自定义的分割线性(通过View实现),因为微信最后一个联系人的分割线是宽度充满屏幕的,要单独定义,最后一个是统计联系人的TextView组件

服务端微信通讯录界面功能实现

服务端的功能和以往文章是类似,就不详细demo了,直接上代码

创建Servlet Contact.java,实现服务端和客户端的数据交互
Contact.java

package com.example.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.pojo.ContactList;
import com.example.service.UserServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLDecoder;

@WebServlet(name = "Contact", value = "/Contact")
public class Contact extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置字符编码,防止中文乱码
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("UTF-8");
        //以json数据完成操作
        response.setContentType("application/json;charset=UTF-8");
        System.out.println(request.getContentType());// 得到客户端发送过来内容的类型,application/json;charset=UTF-8
        System.out.println(request.getRemoteAddr());// 得到客户端的ip地址,
        BufferedReader br = new BufferedReader(new InputStreamReader(// 使用字符流读取客户端发过来的数据
                request.getInputStream()));
        String line = null;
        StringBuffer s = new StringBuffer();//StringBuffer String的区别,如果要对数据作頻繁的修改,則用StringBuffer
        // 以一行的形式读取数据
        while ((line = br.readLine()) != null) {
            s.append(line);
        }
        // 关闭io流
        br.close();
        System.out.println(s.toString());// {"password":"123456","name":"admin"}
        //JSON:这是json解析包,IDEA是没有,要我们自己导入
        ContactList contactList = JSON.parseObject(s.toString(), ContactList.class);//是用了反射机制來完成对象的封闭
        //以utf-8解码操作
        String number = URLDecoder.decode(contactList.getNumber(), "utf-8");
        System.out.println(contactList);
        // 去数据库完成用户登录功能
        UserServiceImpl us = new UserServiceImpl();
        //调用登录的方法
        ContactList contactList1 = us.contact(number);
        if(contactList1  != null) {
            //将结果返回给客户端	,將結果構建成json數據返回給客戶端
            JSONObject rjson = new JSONObject();
            rjson.put("json", contactList1 );
            response.getOutputStream().write(
                    rjson.toString().getBytes("UTF-8"));// 向客户端发送一个带有json对象内容的响应
        }
    }
}

上面代码用到微信通讯录界面的实体类,下面将给出

实体类ContactList.java
ContactList.java

package com.example.pojo;

public class ContactList {
    private int id;
    private String img;
    private String name;
    private String number;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getImg() {
        return img;
    }

    public void setImg(String img) {
        this.img = img;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "ContactList{" +
                "id=" + id +
                ", img='" + img + '\'' +
                ", name='" + name + '\'' +
                ", number='" + number + '\'' +
                '}';
    }
}

在service层中的接口UserService.java添加处理微信通讯录界面数据业务逻辑处理的抽象方法

    //微信通讯录
    ContactList contact(String number);

在service层中的类UserServiceImpl.java重写上面接口刚添加的方法

    public ContactList contact(String number) {

        //调用dao层完成数据查询操作
        ContactList contactList = ud.findContact(number);

        return contactList;
    }

在dao层中的接口UserDao .java添加处理微信通信录界面数据并操作数据库的的抽象方法

    //查询微信通信录列表
    ContactList findContact(String number);

在dao层中的类UserDaoImpl.java重写上面接口刚添加的方法

    @Override
    public ContactList findContact(String number) {

        String sql = "select * from contact where number=?;";
        ResultSet rs = JDBCUtil.executeQuery(sql, number);
        //判断是否查询到用户
        try {
            if (rs.next()) {
                //如果查询到用户,将用户封装到User对象中
                int id = rs.getInt("id");
                String img = rs.getString("img");
                String name = rs.getString("name");
                String number1 = rs.getString("number");
                //将查询到的用户封装到一个User对象中
                ContactList contactList = new ContactList();
                contactList .setId(id);
                contactList .setImg(img);
                contactList .setName(name);
                contactList .setNumber(number1);
                System.out.println("查询到的用户" + contactList);
                return contactList;
            }
        }catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

在imgs目录下创建存放通讯录微信头像的目录,之后再往里添加图片即可
在这里插入图片描述

下面给出我的表结构以及表内容

在这里插入图片描述

在这里插入图片描述

每一条记录对应一个用户,客户端通过微信号向服务器发送请求,服务器接受请求后向数据库进行查找,如果查找成功将返回一条记录给服务端,客户端进行解析分离

测试

测试前要事先在数据库里添加数据

在这里插入图片描述

评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

stormzhuo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值