介绍

RecyclerView用于在有限的窗口展现大量的数据,其实早已经有了类似的控件,如ListView、GridView,那么相比它们,RecyclerView有什么样优势呢?
RecyclerView标准化了ViewHolder,而且异常的灵活,可以轻松实现ListView实现不了的样式和功能,通过布局管理器LayoutManager可控制Item的布局方式,通过设置Item操作动画自定义Item添加和删除的动画,通过设置Item之间的间隔样式,自定义间隔。

可实现效果

  • 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式。

  • 可设置Item操作的动画(删除或者添加等)

  • 可设置Item的间隔样式(可绘制)

关于Item的点击和长按事件,需要用户自己去实现

使用

  • 使用RecyclerView时候,必须指定一个适配器Adapter和一个布局管理器LayoutManager。
  • 适配器继承RecyclerView.Adapter类,具体实现类似ListView的适配器,取决于数据信息以及展示的UI。
  • 布局管理器用于确定RecyclerView中Item的展示方式以及决定何时复用已经不可见的Item,避免重复创建以及执行高成本的findViewById()方法

用法

示例

mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
LinearLayoutManager mLayoutManager=new LinearLayoutManager(this);
// 设置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 设置adapter
mRecyclerView.setAdapter(mAdapter);
// 设置Item添加和移除的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// 设置Item之间间隔样式
mRecyclerView.addItemDecoration(mDividerItemDecoration);

基本使用

首先需要在在 build.gradle 文件中引入 RecyclerView 类

  compile 'com.android.support:recyclerview-v7:23.4.0'

Fragment代码

package com.demo.fragment;import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;import com.demo.R;
import com.demo.adapter.VideoRecyclerViewAdapter;
import com.demo.bean.VideoBean;import java.util.ArrayList;
import java.util.List;public class ListViewFragment extends Fragment{public static ListViewFragment newInstance() {return new ListViewFragment();}@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {View view = inflater.inflate(R.layout.activity_recycler_view, container, false);initView(view);return view;}private void initView(View view) {RecyclerView recyclerView = view.findViewById(R.id.rv);LinearLayoutManager layoutManager=new LinearLayoutManager(getActivity());layoutManager.setOrientation(LinearLayoutManager.VERTICAL);recyclerView.setLayoutManager(layoutManager);// 设置布局管理器DefaultItemAnimator itemAnimator = new DefaultItemAnimator();recyclerView .setItemAnimator(itemAnimator);// 设置Item添加和移除的动画itemAnimator.setSupportsChangeAnimations(false);recyclerView.setAdapter(new VideoRecyclerViewAdapter(getVideoList(), getActivity()));}public List<VideoBean> getVideoList() {List<VideoBean> videoList = new ArrayList<>();//...添加数据return videoList;}
}

R.layout.activity_recycler_view

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><android.support.v7.widget.RecyclerView
        android:id="@+id/rv"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>

RecyclerView适配器Adapter代码

package com.demo.adapter;import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;import com.bumptech.glide.Glide;
import com.demo.R;
import com.demo.bean.VideoBean;
import com.demo.view.CustomVideoView ;import java.util.List;public class VideoRecyclerViewAdapter extends RecyclerView.Adapter<VideoRecyclerViewAdapter.VideoHolder> {private List<VideoBean> videos;private Context context;public VideoRecyclerViewAdapter(List<VideoBean> videos, Context context) {this.videos = videos;this.context = context;}@Overridepublic VideoHolder onCreateViewHolder(ViewGroup parent, int viewType) {View itemView = LayoutInflater.from(context).inflate(R.layout.item_video_auto_play, parent, false);return new VideoHolder(itemView);}@Overridepublic void onBindViewHolder(final VideoHolder holder, int position) {VideoBean videoBean = videos.get(position);holder.title.setText(videoBean.getTitle());holder.videoView .setPlayUrl(videoBean.getUrl());}@Overridepublic int getItemCount() {return videos.size();}public class VideoHolder extends RecyclerView.ViewHolder {private CustomVideoView videoView;private TextView title;VideoHolder(View itemView) {super(itemView);videoView= (CustomVideoView )itemView.findViewById(R.id.video_player);int widthPixels = context.getResources().getDisplayMetrics().widthPixels;videoView .setLayoutParams(new LinearLayout.LayoutParams(widthPixels, widthPixels / 16 * 9));title = itemView.findViewById(R.id.tv_title);}}}

布局管理器:RecyclerView.LayoutManager

上述代码中mLayoutManager 对象是布局管理器,RecyclerView提供了三种布局管理器:

  • LinerLayoutManager(线性):以垂直或者水平列表方式展示Item
  • GridLayoutManager (网格):以网格方式展示Item
  • StaggeredGridLayoutManager(瀑布流): 以瀑布流方式展示Item

适配器:RecyclerView.Adapter

RecyclerView.Adapter的使用方式和ListView的ListAdapter 类似,向RecyclerView提供显示的数据。
但是RecyclerView.Adapter做了一件了不起的优化,那就是RecyclerView.Adapter的

 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 

方法能够保证当前RecyclerView是确实需要你创建一个新的ViewHolder对象。而ListView的ListAdapter 对应的方法

    @Overridepublic View getView(int i, View view, ViewGroup viewGroup)

需要在方法内部判断是重新创建一个View还是刷新一个View的数据,而不明所以的客户可能每次都会返回一个新创建的View造成ListView的卡顿和资源的浪费;创建和刷新作为两个不同的功能本来就应该在两个方法中实现—庆幸的是RecyclerView.Adapter解决了这个问题

视图容器:RecyclerView.ViewHolder

RecyclerView中强制客户使用ViewHolder,谈及ListView的时候就经常说到使用ViewHolder来进行优化。使用ViewHolder其中一点好处是能够避免重复调用方法findViewById(),对当前item的View中的子View进行管理。
ViewHolder 描述RecylerView中某个位置的itemView和元数据信息,属于Adapter的一部分。其实该类通常用于保存 findViewById 的结果
ViewHolder的mFlags属性

  • FLAG_BOUND ——ViewHolder已经绑定到某个位置,mPosition、mItemId、mItemViewType都有效
  • FLAG_UPDATE ——ViewHolder绑定的View对应的数据过时需要重新绑定,mPosition、mItemId还是一致的
  • FLAG_INVALID ——ViewHolder绑定的View对应的数据无效,需要完全重新绑定不同的数据
  • FLAG_REMOVED ——ViewHolder对应的数据已经从数据集移除
  • FLAG_NOT_RECYCLABLE ——ViewHolder不能复用
  • FLAG_RETURNED_FROM_SCRAP ——这个状态的ViewHolder会加到scrap list被复用。
  • FLAG_CHANGED ——ViewHolder内容发生变化,通常用于表明有ItemAnimator动画
  • FLAG_IGNORE ——ViewHolder完全由LayoutManager管理,不能复用
  • FLAG_TMP_DETACHED ——ViewHolder从父RecyclerView临时分离的标志,便于后续移除或添加回来
  • FLAG_ADAPTER_POSITION_UNKNOWN ——ViewHolder不知道对应的Adapter的位置,直到绑定到一个新位置
  • FLAG_ADAPTER_FULLUPDATE ——方法 addChangePayload(null) 调用时设置

间隔样式:RecyclerView.ItemDecoration

  • 用于绘制itemView之间的一些特殊UI,比如在itemView之前设置空白区域、画线等。
  • RecyclerView 将itemView和装饰UI分隔开来,装饰UI即 ItemDecoration ,主要用于绘制item间的分割线、高亮或者margin等
  • 通过recyclerView.addItemDecoration(new DividerDecoration(this))对item添加装饰;对RecyclerView设置多个ItemDecoration,列表展示的时候会遍历所有的ItemDecoration并调用里面的绘制方法,对Item进行装饰。
  • public void onDraw(Canvas c, RecyclerView parent) 装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡
  • public void onDrawOver(Canvas c, RecyclerView parent) 装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上
  • public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量

展示效果和ListView基本上无差别,但是Item之间并没有分割线,在xml去找divider属性的时候,发现RecyclerView没有divider属性,当然也可以在Item布局中加上分割线,但是这样做并不是很优雅。
其实RecyclerView是支持自定义间隔样式的。通过

mRecyclerView.addItemDecoration()

来设置我们定义好的间隔样式

自定义间隔样式需要继承RecyclerView.ItemDecoration类,该类是个抽象类,主要有三个方法

  • onDraw(Canvas c, RecyclerView parent, State state):在Item绘制之前被调用,该方法主要用于绘制间隔样式
  • onDrawOver(Canvas c, RecyclerView parent, State state):在Item绘制之前被调用,该方法主要用于绘制间隔样式
  • getItemOffsets(Rect outRect, View view, RecyclerView parent, State state):设置item的偏移量,偏移的部分用于填充间隔样式,在RecyclerView的onMesure()中会调用该方法

onDraw()和onDrawOver()这两个方法都是用于绘制间隔样式,我们只需要复写其中一个方法即可。直接来看一下自定义的间隔样式的实现,参考官方实例

public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {private static final int[] ATTRS = new int[]{android.R.attr.listDivider};public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;/*** 用于绘制间隔样式*/private Drawable mDivider;/*** 列表的方向,水平/竖直*/private int mOrientation;public MyDividerItemDecoration(Context context, int orientation) {// 获取默认主题的属性final TypedArray a = context.obtainStyledAttributes(ATTRS);mDivider = a.getDrawable(0);a.recycle();setOrientation(orientation);}@Overridepublic void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {// 绘制间隔if (mOrientation == VERTICAL_LIST) {drawVertical(c, parent);} else {drawHorizontal(c, parent);}}@Overridepublic void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {if (mOrientation == VERTICAL_LIST) {outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());} else {outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);}}private void setOrientation(int orientation) {if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {throw new IllegalArgumentException("invalid orientation");}mOrientation = orientation;}/*** 绘制间隔*/private void drawVertical(Canvas c, RecyclerView parent) {final int left = parent.getPaddingLeft();final int right = parent.getWidth() - parent.getPaddingRight();final int childCount = parent.getChildCount();for (int i = 0; i < childCount; i++) {final View child = parent.getChildAt(i);final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();final int top = child.getBottom() + params.bottomMargin +Math.round(ViewCompat.getTranslationY(child));final int bottom = top + mDivider.getIntrinsicHeight();mDivider.setBounds(left, top, right, bottom);mDivider.draw(c);}}/*** 绘制间隔*/private void drawHorizontal(Canvas c, RecyclerView parent) {final int top = parent.getPaddingTop();final int bottom = parent.getHeight() - parent.getPaddingBottom();final int childCount = parent.getChildCount();for (int i = 0; i < childCount; i++) {final View child = parent.getChildAt(i);final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();final int left = child.getRight() + params.rightMargin +Math.round(ViewCompat.getTranslationX(child));final int right = left + mDivider.getIntrinsicHeight();mDivider.setBounds(left, top, right, bottom);mDivider.draw(c);}}
}

然后在代码中设置RecyclerView的间隔样式

mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));

动画:RecyclerView.ItemAnimator

RecyclerView可以设置列表中Item删除和添加的动画,在v7包中给我们提供了一种默认的Item删除和添加的动画,如果没有特殊的需求,默认使用这个动画即可

// 设置Item添加和移除的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

设置的动画用于在 item 项数据变化时的动画效果
当调用 Adapter 的 notifyItemChanged、notifyItemInserted、notifyItemMoved 等方法,会触发该对象显示相应的动画。
RecyclerView 的 ItemAnimator 使得 item 的动画实现变得简单而样式丰富,我们可以自定义 item 项不同操作(如添加,删除)的动画效果;
ItemAnimator 触发于以下三种事件:

  • 某条数据被插入到数据集合中 ,对应 public final void notifyItemInserted(int position) 方法
  • 从数据集合中移除某条数据 ,对应 public final void notifyItemRemoved(int position) 方法
  • 更改数据集合中的某条数据,对应 public final void notifyItemChanged(int position) 方法

注意:notifyDataSetChanged(),会触发列表的重绘,并不会出现任何动画效果
使用:Animator使用到的逻辑比较多,因此最方便的就是使用第三方库:https://github.com/wasabeef/recyclerview-animators

点击事件

RecyclerView并没有像ListView一样暴露出Item点击事件或者长按事件处理的api,也就是说使用RecyclerView时候,需要我们自己来实现Item的点击和长按等事件的处理。
实现方法有很多

  • 可以监听RecyclerView的Touch事件然后判断手势做相应的处理
  • 可以通过在绑定ViewHolder的时候设置监听,然后通过Apater回调出去
  • 使用点击、长按事件支持类

第二种方法:在绑定ViewHolder的时候设置监听,通过Apater回调出去 Adapter 的完整代码

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter .ViewHolder>{/*** 展示数据*/private ArrayList<String> mData;/*** 事件回调监听*/private RecyclerViewAdapter.OnItemClickListener onItemClickListener;public RecyclerViewAdapter(ArrayList<String> data) {this.mData = data;}public void updateData(ArrayList<String> data) {this.mData = data;notifyDataSetChanged();}/*** 添加新的Item*/public void addNewItem() {if(mData == null) {mData = new ArrayList<>();}mData.add(0, "new Item");notifyItemInserted(0);}/*** 删除Item*/public void deleteItem() {if(mData == null || mData.isEmpty()) {return;}mData.remove(0);notifyItemRemoved(0);}/*** 设置回调监听* * @param listener*/public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {this.onItemClickListener = listener;}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {// 实例化展示的viewView v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);// 实例化viewholderViewHolder viewHolder = new ViewHolder(v);return viewHolder;}@Overridepublic void onBindViewHolder(final ViewHolder holder, int position) {// 绑定数据holder.mTv.setText(mData.get(position));holder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(final View v) {if(onItemClickListener != null) {int pos = holder.getLayoutPosition();onItemClickListener.onItemClick(holder.itemView, pos);}}});holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {if(onItemClickListener != null) {int pos = holder.getLayoutPosition();onItemClickListener.onItemLongClick(holder.itemView, pos);}//表示此事件已经消费,不会触发单击事件return true;}});}@Overridepublic int getItemCount() {return mData == null ? 0 : mData.size();}public static class ViewHolder extends RecyclerView.ViewHolder {TextView mTv;public ViewHolder(View itemView) {super(itemView);mTv = (TextView) itemView.findViewById(R.id.item_tv);}}public interface OnItemClickListener {void onItemClick(View view, int position);void onItemLongClick(View view, int position);}
}

Activity 设置 Adapter 事件监听

mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {@Overridepublic void onItemClick(View view, int position) {Toast.makeText(MyActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();}@Overridepublic void onItemLongClick(View view, int position) {Toast.makeText(MyActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();}
});

第三种方法:使用点击、长按事件支持类代码

参考 Hugo 的文章:Getting your clicks on RecyclerView

  1. 先要准备一份resources
    res -> values -> ids.xml ->
<?xml version="1.0" encoding="utf-8"?>
<resources><item name="item_click_support" type="id" />
</resources>
  1. 具体的支持类
public class ItemClickSupport {private final RecyclerView mRecyclerView;private OnItemClickListener mOnItemClickListener;private OnItemLongClickListener mOnItemLongClickListener;private View.OnClickListener mOnClickListener = new View.OnClickListener() {@Override public void onClick(View v) {if (mOnItemClickListener != null) {RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);}}};private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {@Override public boolean onLongClick(View v) {if (mOnItemLongClickListener != null) {RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);}return false;}};private RecyclerView.OnChildAttachStateChangeListener mAttachListener = new RecyclerView.OnChildAttachStateChangeListener() {@Override public void onChildViewAttachedToWindow(View view) {if (mOnItemClickListener != null) {view.setOnClickListener(mOnClickListener);}if (mOnItemLongClickListener != null) {view.setOnLongClickListener(mOnLongClickListener);}}@Override public void onChildViewDetachedFromWindow(View view) {}};private ItemClickSupport(RecyclerView recyclerView) {mRecyclerView = recyclerView;mRecyclerView.setTag(R.id.item_click_support, this);mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);}public static ItemClickSupport addTo(RecyclerView view) {ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);if (support == null) {support = new ItemClickSupport(view);}return support;}public static ItemClickSupport removeFrom(RecyclerView view) {ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);if (support != null) {support.detach(view);}return support;}public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {mOnItemClickListener = listener;return this;}public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {mOnItemLongClickListener = listener;return this;}private void detach(RecyclerView view) {view.removeOnChildAttachStateChangeListener(mAttachListener);view.setTag(R.id.item_click_support, null);}// 点击接口public interface OnItemClickListener {void onItemClicked(RecyclerView recyclerView, int position, View v);}// 长按接口public interface OnItemLongClickListener {boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);}
}
  1. 在setAdapter()之后调用
// 点击
ItemClickSupport.addTo(rv).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {@Override public void onItemClicked(RecyclerView recyclerView, int position, View v) {Toast.makeText(MainActivity.this, mDatas.get(position), Toast.LENGTH_SHORT).show();}
});// 长按
ItemClickSupport.addTo(rv).setOnItemLongClickListener(new ItemClickSupport.OnItemLongClickListener() {@Override public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {Toast.makeText(MainActivity.this, "长按" + mDatas.get(position) + "已删除", Toast.LENGTH_SHORT).show();// 需要自己处理position在集合中的位置(需考虑头、身、脚布局数量)mDatas.remove(position);if (lastVisible + 1 == mAdapter.getItemCount()) {addmore();}mAdapter.notifyItemRemoved(position);// 消耗事件return true;}
});

总结

目前而言,我们已经知道RecyclerView的一些功能如下

  • 水平列表展示,设置LayoutManager的方向性
  • 竖直列表展示,设置LayoutManager的方向性
  • 自定义间隔,RecyclerView.addItemDecoration()
  • Item添加和删除动画,RecyclerView.setItemAnimator()

所以在项目中如果再遇见列表类的布局,就可以优先考虑使用 RecyclerView,更灵活更快捷的使用方式会给编码带来不一样的体验

Android RecyclerView详解相关推荐

  1. 【转】Android菜单详解——理解android中的Menu--不错

    原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...

  2. Android菜单详解——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

  3. Android LayoutInflater详解

    Android LayoutInflater详解 在实际开发中LayoutInflater这个类还是非常有用的,它的作用类 似于findViewById().不同点是LayoutInflater是用来 ...

  4. android Fragments详解

    android Fragments详解一:概述 android Fragments详解二:创建Fragment 转载于:https://my.oschina.net/liangzhenghui/blo ...

  5. android WebView详解,常见漏洞详解和安全源码(下)

    上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑.  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:http ...

  6. android WebView详解,常见漏洞详解和安全源码(上)

    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析.  由于博客内容长度,这次将分为上下两篇,上篇详解 WebView ...

  7. android子视图无菜单,Android 菜单详解

    Android中菜单分为三种,选项菜单(OptionMenu),上下文菜单(ContextMenu),子菜单(SubMenu) 选项菜单 可以通过两种办法增加选项菜单,一是在menu.xml中添加,该 ...

  8. Android StateFlow详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121913352 本文出自[赵彦军的博客] 文章目录 系列文章 一.冷流还是热流 S ...

  9. Android SharedFlow详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121911675 本文出自[赵彦军的博客] 文章目录 系列文章 什么是SharedF ...

最新文章

  1. Java项目:朴素风个人博客系统(前后端分离+java+vue+Springboot+ssm+mysql+maven+redis)
  2. dedecms模板中首页实现分页的方法
  3. 索引,表增删改统计,加锁查具体情况(推荐)
  4. Cloud for Customer Fiori client导航栏里工作中心层级显示设定
  5. 国产数据库技术全面破冰,金融核心系统打破国外巨头垄断指日可待
  6. 程序员又迎一利器,联想 LeapIOT 工业互联网平台大曝光
  7. 非资深玩家留言频率限制(1024秒限制)
  8. 新兴的人工智能服务器,5个新兴人工智能物联网应用
  9. java 切图_分布式切图服务——切图篇
  10. hadoop版本和java版本不一致的问题
  11. MySQL初始密码忘记了怎么办
  12. zmud之汉字转换为数字
  13. 基于Android的学生信息管理大作业
  14. 【VR】一直困扰虚拟现实的VAC现象,真的无解么?
  15. 国产系统-Deepin安装图文(VIP典藏2022版)
  16. 一周学python系列(7)——面向对象
  17. 银行间市场的USDCNY即期一天交易量到底有多少?
  18. Oracle数据库配置
  19. java中StringTokenizer使用
  20. swagger生成对应的客户端、服务端代码

热门文章

  1. rpm升级时spec文件执行的流程
  2. (十一)Json文件配置
  3. Docker端口映射实现
  4. 《linux文件权限管理大总结》RHEL6
  5. QQ号码采集及邮件发送系统2009
  6. Android高级模糊技术RenderScript和FastBlur
  7. (53)zabbix模板
  8. WPF 绑定以基础数据类型为集合的无字段名的数据源
  9. linux下执行scrapy的爬虫定时任务
  10. POJ 2186 挑战 --牛红人 强连通分量——Tarjan