06.实例篇:仿QQ好友列表——ExpandableListView和ListView(下篇)

本文详细介绍了Android中ListView和ExpandableListView的基础知识,包括它们的用途、关键方法、重要属性以及使用方式。着重解释了如何通过Adapter绑定数据、处理点击事件、动态加载网络数据等核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.前言

本篇主要ExpandableListView和ListView一些基础知识点介绍,主要包括一些类和接口的使用介绍,主要是参照google 最新的API文档摘要总结的;


2.什么是ListView

是一种ViewGroup,用于滚动展示一组列表,这些列表项自动由Adapter从数据源读取数据产生特定的View填充到ListView;
当布局中的内容是动态的或者预先不可知的,可以使用AdapterView(ListView,GridView是其子类)运行时填充;
AdapterView(包括子类)使用Adapter绑定数据到自己,Adapter是数据源和AdapterView布局的中介;


3.ListView重要方法和属性:

  用于垂直滚动列表视图,列表项来自ListAdapter;
  类继承结构:具体的类
  public class  ListView  extends AbsListView 
    java.lang.Object
       ↳ android.view.View
         ↳ android.view.ViewGroup
           ↳ android.widget.AdapterView<T extends android.widget.Adapter>
                ↳ android.widget.AbsListView
                   ↳ android.widget.ListView
  直接子类:
        ExpandableListView :用于展示两级列表;
 
  重要的XML属性:
    android:divider:两个列表项之间的分割线(可以使color或者Drawable)
    android:dividerHeight:分割线的高度(厚度)
    android:entries:静态引用一个数组资源,用来填充ListView的
    android:footerDividersEnabled:对于footer view是否有divider
    android:headerDividersEnabled:
  从AbsListView继承的重要属性:
    android:choiceMode:设置列表项的选择行为,是否可以选择,是否支持多选
    android:listSelector :设置列表中被选中的项的Drawable
    android:textFilterEnabled:是否支持过滤


  重要方法:
      void  addFooterView(View v):添加固定的View到列表的底部,在setAdapter之前调用,可以添加多次,显示多个
      void addFooterView(View v, Object data, boolean isSelectable)      
      void addHeaderView(View v)
    
      ListAdapter getAdapter():返回与之绑定的Adapter
      void setAdapter (ListAdapter adapter) :设置ListView的数据

      long[] getCheckItemIds ():api8过期,获取选择的多个Items的id,仅当模式不是CHOICE_MODE_NONE.有效
      void setSelection (int position):设置选定的列表项,从0开始

4.ListView相关的类:ListAdapter(也是接口)

   public interface Adapter  android.widget.Adapter    
   间接子类:
   ArrayAdapter<T>, BaseAdapter, CursorAdapter, HeaderViewListAdapter, ListAdapter,    
   ResourceCursorAdapter, SimpleAdapter, SimpleCursorAdapter,SpinnerAdapter, WrapperListAdapter 
   方法:
   abstract int getCount():有多少数据
   abstract Object getItem(int position):获取特定位置的数据
   abstract long    getItemId(int position):获取特定位置的行id
   abstract View getView(int position, View convertView, ViewGroup parent):获取特定位置展示的View
   abstract boolean isEmpty():
  
   public interface ListAdapter implements Adapter (添加了两个抽象方法) android.widget.ListAdapter
   间接子类:
   ArrayAdapter<T>, BaseAdapter(抽象类), CursorAdapter(抽象类), HeaderViewListAdapter, 
   ResourceCursorAdapter, SimpleAdapter, SimpleCursorAdapter, WrapperListAdapter
     
   常用的Adapter:
   public class ArrayAdapter extends BaseAdapter implements Filterable:android.widget.ArrayAdapter<T>
   具体简单的Adapter类,数据源是数组,布局中有一个简单的TextView,如果想要其它View,可以重写getView方法
   使用一般重写,或者使用:
   public ArrayAdapter (Context context, int resource, T[] objects):Resource布局文件必须有特定名称的textview
   public ArrayAdapter (Context context, int resource, int textViewResourceId, T[] objects)
   public ArrayAdapter (Context context, int resource, List<T> objects)
   public ArrayAdapter (Context context, int resource, int textViewResourceId, List<T> objects)
    
   public class  SimpleCursorAdapter extends ResourceCursorAdapter:

   android.support.v4.widget.SimpleCursorAdapter

   构造器:
   SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags)
   参数:layout-定义列表项的布局文件,c-数据源cursor,可以为null(如果不可用),
             from-数组定义了需要绑定的cursor数据列名,to-表明view的id,用来展示数据
             flags-决定adapter的行为,FLAG_AUTO_REQUERY(过期,不建议使用,会在主线程中获取数据,阻塞)                   FLAG_REGISTER_CONTENT_OBSERVER
(添加内容监听器,当通知到来,

调用onContentChanged方法,小心内存泄露,当使用CursorAdapter[CursorLoader]时,不需要该标志;

   方法:
   Cursor swapCursor(Cursor c):换入一个新的Cursor,返回旧的cursor【未关闭】,如果cursor为null或者新的和旧的一样返回null
   public abstract class  CursorAdapter  extends BaseAdapter implements Filterable


5.ListView使用方式:【读取本地数据】

  •     需要ListView;
  •    需要ListAdapter及其子类;
  •    如果Adpater数据发生变化,应该调用notifyDataSetChanged()方法;
   ListView+ArrayAdapter方法:
   定制每一项文本内容,可以重写数组中每个对象的toString方法;
   定制ListView每一项的视图,可以重写getView方法;
   ArrayAdapter adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, myStringArray);
   ListView listView = (ListView) findViewById(R.id.listview);
   listView.setAdapter(adapter);

    ListView+SimpleCursorAdapter方法:
   String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
                        ContactsContract.CommonDataKinds.Phone.NUMBER};
   int[] toViews = {R.id.display_name, R.id.phone_number};
   SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, 
        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
   ListView listView = getListView();
   listView.setAdapter(adapter);

   处理点击事件:实现AdapterView.onItemClickListener接口
<pre name="code" class="java">    // Create a message handling object as an anonymous class.
    private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
        public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Do something in response to the click
        }
    };

    listView.setOnItemClickListener(mMessageClickedHandler); 
     
使用CursorLoader+ListView方法:    
  public class ListViewLoader extends ListActivity
        implements LoaderManager.LoaderCallbacks<Cursor> {


    // This is the Adapter being used to display the list's data
    SimpleCursorAdapter mAdapter;


    // These are the Contacts rows that we will retrieve
    static final String[] PROJECTION = new String[] {ContactsContract.Data._ID,
            ContactsContract.Data.DISPLAY_NAME};


    // This is the select criteria
    static final String SELECTION = "((" + 
            ContactsContract.Data.DISPLAY_NAME + " NOTNULL) AND (" +
            ContactsContract.Data.DISPLAY_NAME + " != '' ))";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        // Create a progress bar to display while the list loads
        ProgressBar progressBar = new ProgressBar(this);
        progressBar.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT, Gravity.CENTER));
        progressBar.setIndeterminate(true);
        getListView().setEmptyView(progressBar);


        // Must add the progress bar to the root of the layout
        ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
        root.addView(progressBar);


        // For the cursor adapter, specify which columns go into which views
        String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME};
        int[] toViews = {android.R.id.text1}; // The TextView in simple_list_item_1


        // Create an empty adapter we will use to display the loaded data.
        // We pass null for the cursor, then update it in onLoadFinished()
        mAdapter = new SimpleCursorAdapter(this, 
                android.R.layout.simple_list_item_1, null,
                fromColumns, toViews, 0);
        setListAdapter(mAdapter);


        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }


    // Called when a new Loader needs to be created
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        return new CursorLoader(this, ContactsContract.Data.CONTENT_URI,
                PROJECTION, SELECTION, null, null);
    }


    // Called when a previously created loader has finished loading
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);
    }


    // Called when a previously created loader is reset, making the data unavailable
    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }


    @Override 
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Do something when a list item is clicked
    }
}  

5.ListView动态获取网络数据方式【见下一节文章】

6.ExpandableListView相关类和知识点

   public class  ExpandableListView extends ListView:ListView的子类,android.widget.ExpandableListView
   两级列表,分为组和组中Items,使用ExpandableListAdapter接口提供数据;

   重要的接口:
   ExpandableListView.OnChildClickListener 、ExpandableListView.OnGroupClickListener
   ExpandableListView.OnGroupCollapseListener 、ExpandableListView.OnGroupExpandListener


   重要的XML属性:
   android:childDivider:color或者Drawable资源文件,子项目的分隔符
   android:childIndicator :状态列表的Drawable资源文件,用于定义子项目各种状态对应的效果
   android:groupIndicator 


   重要方法:
   ExpandableListAdapter getExpandableListAdapter():
   boolean collapseGroup(int groupPos)
   boolean expandGroup(int groupPos)
   boolean expandGroup(int groupPos, boolean animate)
   long getSelectedPosition():获取选择Group或者child的位置,该位置是打包以后的;综合的
   long getSelectedId ():获取已经选择的Group或者child的id,如果没选择,则返回-1;
   static int getPackedPositionChild(long packedPosition)  
   public boolean setSelectedChild (int groupPosition, int childPosition, boolean shouldExpandGroup)
   public void setSelectedGroup (int groupPosition)

   相关的类public interface ExpandableListAdapter:按Group组织数据,为可展开类别提供数据;
   间接子类:
   BaseExpandableListAdapter, CursorTreeAdapter, ResourceCursorTreeAdapter, 
   SimpleCursorTreeAdapter, SimpleExpandableListAdapter
   
   SimpleExpandableListAdapter:

   注意groupTo和ChildTo所对应的视图都是TextView才行(ImageView不行,其它类型的视图,需要重写getGroupView和getChildView方法;);
       简单的Adapter映射静态数据到group和child view。
       构造器:9个参数

       SimpleExpandableListAdapter(Context context, List<? extends Map<String, ?>> groupData, int groupLayout, 
                String[] groupFrom, int[] groupTo, List<? extends List<? extends Map<String, ?>>> childData,
                 int childLayout, String[] childFrom, int[] childTo)
                 context:AdapterView运行环境上下文
                 groupData:Map的列表,列表中的每一项代表一组所有属性数据,Map包含组的数据,至少包含groupFrom所有的条目,例如每个Group需要一个textView和ImageView,那么可以map1.put("text","xxxx"); map1.put("image", "ddd")作为第一组的group Item;
                 groupLayout:Group视图的布局文件id,至少包含groupTo中的所有组件id
                 groupFrom:一组keys(用于获取每个组的数据,因为一个组项view可能需要多个数据),用于从Maps获取关联的该组view的数据
                 groupTo:一组view id,用于对应展示groupFrom中列数据
                 childData:Map对象列表的列表,外围的列表代表每个组,里面的列表代表每一组里面的项所有属性数据;

 其它参数类似;   

       SimpleExpandableListAdapter(Context context, List<? extends Map<String, ?>> groupData, 
                int expandedGroupLayout, int collapsedGroupLayout, String[] groupFrom,
                int[] groupTo, List<? extends List<? extends Map<String, ?>>> childData, 
                int childLayout, String[] childFrom, int[] childTo)


       SimpleExpandableListAdapter(Context context, List<? extends Map<String, ?>> groupData,
                int expandedGroupLayout, int collapsedGroupLayout, String[] groupFrom, int[] groupTo, 
                List<? extends List<? extends Map<String, ?>>> childData, int childLayout, 
                int lastChildLayout, String[] childFrom, int[] childTo)

        SimpleCursorTreeAdapter:
        
SimpleCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout,
                 int expandedGroupLayout, String[] groupFrom, int[] groupTo, int childLayout, 
                 int lastChildLayout, String[] childFrom, int[] childTo)


        SimpleCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout, 
                int expandedGroupLayout, String[] groupFrom, int[] groupTo, int childLayout, 
                String[] childFrom, int[] childTo)


        SimpleCursorTreeAdapter(Context context, Cursor cursor, int groupLayout, String[] groupFrom,
                 int[] groupTo, int childLayout, String[] childFrom, int[] childTo)

7.ExpandableListView的使用实例

    参见上篇文章:http://blog.youkuaiyun.com/meplusplus/article/details/26622261

8.如何定制自己的ExpandableListAdapter

     主要是学习系统的源码实现,继承已有的ListAdapter,以下是系统SimpleExpandableListAdapter的源码实现,我们可以继承然后重写getChildView和getGroupView方法;
package android.widget;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;

import java.util.List;
import java.util.Map;

/**
 * An easy adapter to map static data to group and child views defined in an XML
 * file. You can separately specify the data backing the group as a List of
 * Maps. Each entry in the ArrayList corresponds to one group in the expandable
 * list. The Maps contain the data for each row. You also specify an XML file
 * that defines the views used to display a group, and a mapping from keys in
 * the Map to specific views. This process is similar for a child, except it is
 * one-level deeper so the data backing is specified as a List<List<Map>>,
 * where the first List corresponds to the group of the child, the second List
 * corresponds to the position of the child within the group, and finally the
 * Map holds the data for that particular child.
 */
public class SimpleExpandableListAdapter extends BaseExpandableListAdapter {
    private List<? extends Map<String, ?>> mGroupData;
    private int mExpandedGroupLayout;
    private int mCollapsedGroupLayout;
    private String[] mGroupFrom;
    private int[] mGroupTo;
    
    private List<? extends List<? extends Map<String, ?>>> mChildData;
    private int mChildLayout;
    private int mLastChildLayout;
    private String[] mChildFrom;
    private int[] mChildTo;
    
    private LayoutInflater mInflater;
    
    /**
     * Constructor
     * 
     * @param context The context where the {@link ExpandableListView}
     *            associated with this {@link SimpleExpandableListAdapter} is
     *            running
     * @param groupData A List of Maps. Each entry in the List corresponds to
     *            one group in the list. The Maps contain the data for each
     *            group, and should include all the entries specified in
     *            "groupFrom"
     * @param groupFrom A list of keys that will be fetched from the Map
     *            associated with each group.
     * @param groupTo The group views that should display column in the
     *            "groupFrom" parameter. These should all be TextViews. The
     *            first N views in this list are given the values of the first N
     *            columns in the groupFrom parameter.
     * @param groupLayout resource identifier of a view layout that defines the
     *            views for a group. The layout file should include at least
     *            those named views defined in "groupTo"
     * @param childData A List of List of Maps. Each entry in the outer List
     *            corresponds to a group (index by group position), each entry
     *            in the inner List corresponds to a child within the group
     *            (index by child position), and the Map corresponds to the data
     *            for a child (index by values in the childFrom array). The Map
     *            contains the data for each child, and should include all the
     *            entries specified in "childFrom"
     * @param childFrom A list of keys that will be fetched from the Map
     *            associated with each child.
     * @param childTo The child views that should display column in the
     *            "childFrom" parameter. These should all be TextViews. The
     *            first N views in this list are given the values of the first N
     *            columns in the childFrom parameter.
     * @param childLayout resource identifier of a view layout that defines the
     *            views for a child. The layout file should include at least
     *            those named views defined in "childTo"
     */
    public SimpleExpandableListAdapter(Context context,
            List<? extends Map<String, ?>> groupData, int groupLayout,
            String[] groupFrom, int[] groupTo,
            List<? extends List<? extends Map<String, ?>>> childData,
            int childLayout, String[] childFrom, int[] childTo) {
        this(context, groupData, groupLayout, groupLayout, groupFrom, groupTo, childData,
                childLayout, childLayout, childFrom, childTo);
    }

    /**
     * Constructor
     * 
     * @param context The context where the {@link ExpandableListView}
     *            associated with this {@link SimpleExpandableListAdapter} is
     *            running
     * @param groupData A List of Maps. Each entry in the List corresponds to
     *            one group in the list. The Maps contain the data for each
     *            group, and should include all the entries specified in
     *            "groupFrom"
     * @param groupFrom A list of keys that will be fetched from the Map
     *            associated with each group.
     * @param groupTo The group views that should display column in the
     *            "groupFrom" parameter. These should all be TextViews. The
     *            first N views in this list are given the values of the first N
     *            columns in the groupFrom parameter.
     * @param expandedGroupLayout resource identifier of a view layout that
     *            defines the views for an expanded group. The layout file
     *            should include at least those named views defined in "groupTo"
     * @param collapsedGroupLayout resource identifier of a view layout that
     *            defines the views for a collapsed group. The layout file
     *            should include at least those named views defined in "groupTo"
     * @param childData A List of List of Maps. Each entry in the outer List
     *            corresponds to a group (index by group position), each entry
     *            in the inner List corresponds to a child within the group
     *            (index by child position), and the Map corresponds to the data
     *            for a child (index by values in the childFrom array). The Map
     *            contains the data for each child, and should include all the
     *            entries specified in "childFrom"
     * @param childFrom A list of keys that will be fetched from the Map
     *            associated with each child.
     * @param childTo The child views that should display column in the
     *            "childFrom" parameter. These should all be TextViews. The
     *            first N views in this list are given the values of the first N
     *            columns in the childFrom parameter.
     * @param childLayout resource identifier of a view layout that defines the
     *            views for a child. The layout file should include at least
     *            those named views defined in "childTo"
     */
    public SimpleExpandableListAdapter(Context context,
            List<? extends Map<String, ?>> groupData, int expandedGroupLayout,
            int collapsedGroupLayout, String[] groupFrom, int[] groupTo,
            List<? extends List<? extends Map<String, ?>>> childData,
            int childLayout, String[] childFrom, int[] childTo) {
        this(context, groupData, expandedGroupLayout, collapsedGroupLayout,
                groupFrom, groupTo, childData, childLayout, childLayout,
                childFrom, childTo);
    }

    /**
     * Constructor
     * 
     * @param context The context where the {@link ExpandableListView}
     *            associated with this {@link SimpleExpandableListAdapter} is
     *            running
     * @param groupData A List of Maps. Each entry in the List corresponds to
     *            one group in the list. The Maps contain the data for each
     *            group, and should include all the entries specified in
     *            "groupFrom"
     * @param groupFrom A list of keys that will be fetched from the Map
     *            associated with each group.
     * @param groupTo The group views that should display column in the
     *            "groupFrom" parameter. These should all be TextViews. The
     *            first N views in this list are given the values of the first N
     *            columns in the groupFrom parameter.
     * @param expandedGroupLayout resource identifier of a view layout that
     *            defines the views for an expanded group. The layout file
     *            should include at least those named views defined in "groupTo"
     * @param collapsedGroupLayout resource identifier of a view layout that
     *            defines the views for a collapsed group. The layout file
     *            should include at least those named views defined in "groupTo"
     * @param childData A List of List of Maps. Each entry in the outer List
     *            corresponds to a group (index by group position), each entry
     *            in the inner List corresponds to a child within the group
     *            (index by child position), and the Map corresponds to the data
     *            for a child (index by values in the childFrom array). The Map
     *            contains the data for each child, and should include all the
     *            entries specified in "childFrom"
     * @param childFrom A list of keys that will be fetched from the Map
     *            associated with each child.
     * @param childTo The child views that should display column in the
     *            "childFrom" parameter. These should all be TextViews. The
     *            first N views in this list are given the values of the first N
     *            columns in the childFrom parameter.
     * @param childLayout resource identifier of a view layout that defines the
     *            views for a child (unless it is the last child within a group,
     *            in which case the lastChildLayout is used). The layout file
     *            should include at least those named views defined in "childTo"
     * @param lastChildLayout resource identifier of a view layout that defines
     *            the views for the last child within each group. The layout
     *            file should include at least those named views defined in
     *            "childTo"
     */
    public SimpleExpandableListAdapter(Context context,
            List<? extends Map<String, ?>> groupData, int expandedGroupLayout,
            int collapsedGroupLayout, String[] groupFrom, int[] groupTo,
            List<? extends List<? extends Map<String, ?>>> childData,
            int childLayout, int lastChildLayout, String[] childFrom,
            int[] childTo) {
        mGroupData = groupData;
        mExpandedGroupLayout = expandedGroupLayout;
        mCollapsedGroupLayout = collapsedGroupLayout;
        mGroupFrom = groupFrom;
        mGroupTo = groupTo;
        
        mChildData = childData;
        mChildLayout = childLayout;
        mLastChildLayout = lastChildLayout;
        mChildFrom = childFrom;
        mChildTo = childTo;
        
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    
    public Object getChild(int groupPosition, int childPosition) {
        return mChildData.get(groupPosition).get(childPosition);
    }

    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
            View convertView, ViewGroup parent) {
        View v;
        if (convertView == null) {
            v = newChildView(isLastChild, parent);
        } else {
            v = convertView;
        }
        bindView(v, mChildData.get(groupPosition).get(childPosition), mChildFrom, mChildTo);
        return v;
    }

    /**
     * Instantiates a new View for a child.
     * @param isLastChild Whether the child is the last child within its group.
     * @param parent The eventual parent of this new View.
     * @return A new child View
     */
    public View newChildView(boolean isLastChild, ViewGroup parent) {
        return mInflater.inflate((isLastChild) ? mLastChildLayout : mChildLayout, parent, false);
    }
    
    private void bindView(View view, Map<String, ?> data, String[] from, int[] to) {
        int len = to.length;

        for (int i = 0; i < len; i++) {
            TextView v = (TextView)view.findViewById(to[i]);
            if (v != null) {
                v.setText((String)data.get(from[i]));
            }
        }
    }

    public int getChildrenCount(int groupPosition) {
        return mChildData.get(groupPosition).size();
    }

    public Object getGroup(int groupPosition) {
        return mGroupData.get(groupPosition);
    }

    public int getGroupCount() {
        return mGroupData.size();
    }

    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
            ViewGroup parent) {
        View v;
        if (convertView == null) {
            v = newGroupView(isExpanded, parent);
        } else {
            v = convertView;
        }
        bindView(v, mGroupData.get(groupPosition), mGroupFrom, mGroupTo);
        return v;
    }

    /**
     * Instantiates a new View for a group.
     * @param isExpanded Whether the group is currently expanded.
     * @param parent The eventual parent of this new View.
     * @return A new group View
     */
    public View newGroupView(boolean isExpanded, ViewGroup parent) {
        return mInflater.inflate((isExpanded) ? mExpandedGroupLayout : mCollapsedGroupLayout,
                parent, false);
    }

    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }

    public boolean hasStableIds() {
        return true;
    }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值