ListView之多种类型Item

ListView之多种类型Item一、概述一般而言,listview每个item的样式是一样的,但也有很多应用场景下不同位置的item需要不同的样式。拿微信举例,前者的代表作是消息列表,而后者的典型则是聊天会话界面。本文重点介绍

大家好,又见面了,我是你们的朋友全栈君。

一、概述

一般而言,listview每个item的样式是一样的,但也有很多应用场景下不同位置的item需要不同的样式。

拿微信举例,前者的代表作是消息列表,而后者的典型则是聊天会话界面。

本文重点介绍后者,也就是多类型item的listview的实现思路和方法,比如实现一个这样的聊天会话页面:

<span role="heading" aria-level="2">ListView之多种类型Item

 

二、实现思路

2.1 第一种思路用“一种类型”变相实现多种类型

这种思路其实与 ListView之点击展开菜单 这篇文章的原理一样,每个item的布局都包含所有类型的元素:

 <span role="heading" aria-level="2">ListView之多种类型Item

对于每个item,根据实际类型,控制“日期”、“发出的消息”、“接收的消息”这三部分的显示/隐藏即可。

这种思路的优势在于好理解,是单一类型的listview的扩展,却并不适合本文描述的应用场景。

因为每个item实际上只会显示“日期”、“发出的消息”、“接收的消息”中的一种,所以每个item都inflate出来一个“全家桶”layout再隐藏其中的两个,实在是一种资源浪费。

 

2.2 第二种思路:利用Adapter原生支持的多类型

其实 android.widget.Adapter 类已经原生支持了多种类型item的模式,并提供了 int getViewTypeCount();  int getItemViewType(int position); 两个方法

只不过在 android.widget.BaseAdapter 中对这两个方法进行了如下的默认实现:

1 public int getViewTypeCount() {
2     return 1;
3 }
4 
5 public int getItemViewType(int position) {
6     return 0;
7 }

那我们要做的就是根据实际的数据,对这两个方法进行正确的返回。

本文采用第二种思路实现多种类型item的listview。

   [转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

三、开始干活

3.1 首先准备好listview的数据和三种item布局

ListViewMultiTypeActivity$JsonListData:

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

 1     private static class JsonListData {
 2         public static class Message {
 3             public static final int TYPE_COUNT = 3;
 4             public static final int TYPE_DATE = 0x00;
 5             public static final int TYPE_TXT_SENT = 0x01;
 6             public static final int TYPE_TXT_RECV = 0x02;
 7             public int type;
 8             public String txt;
 9             public long time;
10         }
11         public List<Message> messages = new ArrayList<Message>();
12     }

View Code

listview_multitype_data.json:

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

{
    "messages": [
        {
            "type": 0,
            "time": 1467284175
        },
        {
            "type": 1,
            "txt": "你好"
        },
        {
            "type": 2,
            "txt": "你才好"
        },
        {
            "type": 1,
            "txt": "对话,指两个或更多的人用语言交谈,多指小说或戏剧里的人物之间的"
        },
        {
            "type": 2,
            "txt": "京东童书节低至300减180"
        },
        {
            "type": 1,
            "txt": "http://www.cnblogs.com/snser/"
        },
        {
            "type": 2,
            "txt": "京东商城目前已成长为中国最大的自营式电商企业,2015年第三季度在中国自营式B2C电商市场的占有率为56.9%。"
        },
        {
            "type": 0,
            "time": 1467289175
        },
        {
            "type": 1,
            "txt": "京东金融现已建立七大业务板块,分别是供应链金融、消费金融、众筹、财富管理、支付、保险、证券,陆续推出了京保贝、白条、京东钱包、小金库、京小贷、产品众筹、私募股权融资、小白理财等创新产品"
        },
        {
            "type": 2,
            "txt": "您目前没有新消息"
        },
        {
            "type": 2,
            "txt": "黑炎凝聚,竟是直接化为了一头仰天长啸的黑色巨鸟,而后它仿佛是发现了牧尘飘荡的意识,化为一道黑色火焰,眼芒凶狠的对着他的意识暴冲而来"
        },
        {
            "type": 0,
            "time": 1467294175
        },
        {
            "type": 2,
            "txt": "国务院罕见派出民间投资督查组:活力不够形势严峻"
        },
        {
            "type": 1,
            "txt": "那一道清鸣,并不算太过的响亮,但却是让得牧尘如遭雷击,整个身体都是僵硬了下来,脑子里回荡着嗡嗡的声音。"
        },
        {
            "type": 2,
            "txt": "据海关统计,今年前4个月,我国进出口总值7.17万亿元人民币,比去年同期(下同)下降4.4%。其中,出口4.14万亿元,下降2.1%;进口3.03万亿元,下降7.5%;贸易顺差1.11万亿元,扩大16.5%。"
        },
        {
            "type": 1,
            "txt": "在介绍算法的时空复杂度分析方法前,我们先来介绍以下如何来量化算法的实际运行性能,这里我们选取的衡量算法性能的量化指标是它的实际运行时间。"
        },
        {
            "type": 2,
            "txt": "你拍一"
        },
        {
            "type": 2,
            "txt": "我拍一"
        },
        {
            "type": 1,
            "txt": "一二三四五六七"
        }
    ]
}

View Code

ListViewMultiTypeActivity.onCreate 

 1     protected void onCreate(Bundle savedInstanceState) {
 2         super.onCreate(savedInstanceState);
 3         setContentView(R.layout.listview_multi_type);
 4         
 5         JsonListData data = null;
 6         try {
 7             InputStream is = getResources().getAssets().open("listview_multitype_data.json");
 8             InputStreamReader isr = new InputStreamReader(is);
 9             Gson gson = new GsonBuilder().serializeNulls().create();
10             data = gson.fromJson(isr, JsonListData.class);
11         } catch (Exception e) {
12             e.printStackTrace();
13         }
14         
15         if (data != null && data.messages != null) {
16             mList = (ListView)findViewById(R.id.listview_multi_type_list);
17             mList.setAdapter(new MultiTypeAdapter(ListViewMultiTypeActivity.this, data.messages));
18         }
19     }

listview_multi_type_item_date.xml

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_date_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:layout_gravity="center_horizontal"
14         android:layout_margin="6dp"
15         android:padding="3dp"
16         android:background="#CCCCCC"
17         android:textColor="@android:color/white"
18         android:textSize="12sp"
19         android:text="2015年3月25日 18:44" />
20     
21 </LinearLayout>

View Code

listview_multi_type_item_txt_sent.xml

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_txt_sent_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:maxWidth="250dp"
14         android:layout_gravity="right"
15         android:layout_margin="4dp"
16         android:paddingTop="5dp"
17         android:paddingBottom="5dp"
18         android:paddingRight="10dp"
19         android:paddingLeft="5dp"
20         android:background="@drawable/listview_multi_type_item_txt_sent_bg"
21         android:textColor="@android:color/black"
22         android:textSize="13sp"
23         android:text="发出的消息"
24         android:autoLink="web" />
25     
26 </LinearLayout>

View Code

listview_multi_type_item_txt_recv.xml

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_txt_recv_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:maxWidth="250dp"
14         android:layout_gravity="left"
15         android:layout_margin="4dp"
16         android:paddingTop="5dp"
17         android:paddingBottom="5dp"
18         android:paddingRight="5dp"
19         android:paddingLeft="10dp"
20         android:background="@drawable/listview_multi_type_item_txt_recv_bg"
21         android:textColor="@android:color/black"
22         android:textSize="13sp"
23         android:text="接收的消息"
24         android:autoLink="web" />
25     
26 </LinearLayout>

View Code

 

3.2 重头戏在于Adapter的处理

 1 private class MultiTypeAdapter extends BaseAdapter {  2 private LayoutInflater mInflater;  3 private List<JsonListData.Message> mMessages;  4 private SimpleDateFormat mSdfDate = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss", Locale.getDefault());  5  6 public MultiTypeAdapter(Context context, List<JsonListData.Message> messages) {  7 mInflater = LayoutInflater.from(context);  8 mMessages = messages;  9  }  10  11 private class DateViewHolder {  12 public DateViewHolder(View viewRoot) {  13 date = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_date_txt);  14  }  15 public TextView date;  16  }  17  18 private class TxtSentViewHolder {  19 public TxtSentViewHolder(View viewRoot) {  20 txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_sent_txt);  21  }  22 public TextView txt;  23  }  24  25 private class TxtRecvViewHolder {  26 public TxtRecvViewHolder(View viewRoot) {  27 txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_recv_txt);  28  }  29 public TextView txt;  30  }  31  32  @Override  33 public int getViewTypeCount() {  34 return JsonListData.Message.TYPE_COUNT;  35  }  36  37  @Override  38 public int getItemViewType(int position) {  39 return getItem(position).type;  40  }  41  42  @Override  43 public int getCount() {  44 return mMessages.size();  45  }  46  47  @Override  48 public JsonListData.Message getItem(int position) {  49 return mMessages.get(position);  50  }  51  52  @Override  53 public long getItemId(int position) {  54 return position;  55  }  56  57  @Override  58 public View getView(int position, View convertView, ViewGroup parent) {  59 switch (getItemViewType(position)) {  60 case JsonListData.Message.TYPE_DATE:  61 return handleGetDateView(position, convertView, parent);  62 case JsonListData.Message.TYPE_TXT_SENT:  63 return handleGetTxtSentView(position, convertView, parent);  64 case JsonListData.Message.TYPE_TXT_RECV:  65 return handleGetTxtRecvView(position, convertView, parent);  66 default:  67 return null;  68  }  69  }  70  71 private View handleGetDateView(int position, View convertView, ViewGroup parent) {  72 if (convertView == null) {  73 convertView = mInflater.inflate(R.layout.listview_multi_type_item_date, parent, false);  74 convertView.setTag(new DateViewHolder(convertView));  75  }  76 if (convertView != null && convertView.getTag() instanceof DateViewHolder) {  77 final DateViewHolder holder = (DateViewHolder)convertView.getTag();  78  holder.date.setText(formatTime(getItem(position).time));  79  }  80 return convertView;  81  }  82  83 private View handleGetTxtSentView(int position, View convertView, ViewGroup parent) {  84 if (convertView == null) {  85 convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_sent, parent, false);  86 convertView.setTag(new TxtSentViewHolder(convertView));  87  }  88 if (convertView != null && convertView.getTag() instanceof TxtSentViewHolder) {  89 final TxtSentViewHolder holder = (TxtSentViewHolder)convertView.getTag();  90  holder.txt.setText(getItem(position).txt);  91  }  92 return convertView;  93  }  94  95 private View handleGetTxtRecvView(int position, View convertView, ViewGroup parent) {  96 if (convertView == null) {  97 convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_recv, parent, false);  98 convertView.setTag(new TxtRecvViewHolder(convertView));  99  } 100 if (convertView != null && convertView.getTag() instanceof TxtRecvViewHolder) { 101 final TxtRecvViewHolder holder = (TxtRecvViewHolder)convertView.getTag(); 102  holder.txt.setText(getItem(position).txt); 103  } 104 return convertView; 105  } 106 107 private String formatTime(long time) { 108 return mSdfDate.format(new Date(time * 1000)); 109  } 110 }

可以看到, int getViewTypeCount();  int getItemViewType(int position); 的处理是非常清晰的。

需要注意的在于,ViewType必须在 [0, getViewTypeCount() – 1] 范围内

 

3.3 ViewHolder为何能正确的工作

回顾一下单一类型的listview,其ViewHolder的工作机制在于系统会将滑出屏幕的item的view回收起来,并作为getView的第二个参数 convertView 传入。

那么,在多种类型的listview中,滑出屏幕的view与即将滑入屏幕的view类型很可能是不同的,那这么直接用不就挂了吗?

其实不然,android针对多种类型item的情况已经做好处理了,如果getView传入的 convertView 不为null,那它一定与当前item的view类型是匹配的。

所以,在3.2节中对ViewHolder的处理方式与单类型的listview并没有本质区别,却也能正常的工作。

  [转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

四、demo工程

保存下面的图片,扩展名改成 .zip 即可

<span role="heading" aria-level="2">ListView之多种类型Item

  [转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

五、番外篇 —— ListView回收机制简要剖析

在3.3节中简单介绍了android系统会处理好多类型item的回收和重用,那具体是怎么实现的呢?

下面简要剖析一下支持多种类型item的listview中,View回收的工作机制。

5.1 View回收站的初始化

ListView的父类AbsListView中定义了一个内部类RecycleBin,这个类维护了listview滑动过程中,view的回收和重用。

在ListView的 setAdapter 方法中,会通过调用 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()) 来初始化RecycleBin。

让我们看下RecycleBin中对应都做了什么:

 1 public void setViewTypeCount(int viewTypeCount) {  2 if (viewTypeCount < 1) {  3 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");  4  }  5 //noinspection unchecked  6 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];  7 for (int i = 0; i < viewTypeCount; i++) {  8 scrapViews[i] = new ArrayList<View>();  9  } 10 mViewTypeCount = viewTypeCount; 11 mCurrentScrap = scrapViews[0]; 12 mScrapViews = scrapViews; 13 }

看源码,说白了就是创建了一个大小为 getViewTypeCount() 数组 mScrapViews ,从而为每种类型的view维护了一个回收站,此外每种类型的回收站自身又是一个View数组。

这也就解释了为什么ViewType必须在 [0, getViewTypeCount() – 1] 范围内。

 

5.2 View回收站的构建和维护

AbsListView在滑动时,会调用 trackMotionScroll 方法:

 1 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {  2 //...  3 final boolean down = incrementalDeltaY < 0;  4 //...  5 if (down) {  6 int top = -incrementalDeltaY;  7 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {  8 top += listPadding.top;  9  } 10 for (int i = 0; i < childCount; i++) { 11 final View child = getChildAt(i); 12 if (child.getBottom() >= top) { 13 break; 14 } else { 15 count++; 16 int position = firstPosition + i; 17 if (position >= headerViewsCount && position < footerViewsStart) { 18 // The view will be rebound to new data, clear any 19 // system-managed transient state. 20 if (child.isAccessibilityFocused()) { 21  child.clearAccessibilityFocus(); 22  } 23  mRecycler.addScrapView(child, position); 24  } 25  } 26  } 27 } else { 28 int bottom = getHeight() - incrementalDeltaY; 29 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 30 bottom -= listPadding.bottom; 31  } 32 for (int i = childCount - 1; i >= 0; i--) { 33 final View child = getChildAt(i); 34 if (child.getTop() <= bottom) { 35 break; 36 } else { 37 start = i; 38 count++; 39 int position = firstPosition + i; 40 if (position >= headerViewsCount && position < footerViewsStart) { 41 // The view will be rebound to new data, clear any 42 // system-managed transient state. 43 if (child.isAccessibilityFocused()) { 44  child.clearAccessibilityFocus(); 45  } 46  mRecycler.addScrapView(child, position); 47  } 48  } 49  } 50  } 51 //... 52 }

 trackMotionScroll 方法中,会根据不同的滑动方向,调用 addScrapView ,将滑出屏幕的view加到RecycleBin中:

 1 void addScrapView(View scrap, int position) {  2 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();  3 if (lp == null) {  4 return;  5  }  6  7 lp.scrappedFromPosition = position;  8  9 // Remove but don't scrap header or footer views, or views that 10 // should otherwise not be recycled. 11 final int viewType = lp.viewType; 12 if (!shouldRecycleViewType(viewType)) { 13 return; 14  } 15 16  scrap.dispatchStartTemporaryDetach(); 17 18 // The the accessibility state of the view may change while temporary 19 // detached and we do not allow detached views to fire accessibility 20 // events. So we are announcing that the subtree changed giving a chance 21 // to clients holding on to a view in this subtree to refresh it. 22  notifyViewAccessibilityStateChangedIfNeeded( 23  AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); 24 25 // Don't scrap views that have transient state. 26 final boolean scrapHasTransientState = scrap.hasTransientState(); 27 if (scrapHasTransientState) { 28 if (mAdapter != null && mAdapterHasStableIds) { 29 // If the adapter has stable IDs, we can reuse the view for 30 // the same data. 31 if (mTransientStateViewsById == null) { 32 mTransientStateViewsById = new LongSparseArray<View>(); 33  } 34  mTransientStateViewsById.put(lp.itemId, scrap); 35 } else if (!mDataChanged) { 36 // If the data hasn't changed, we can reuse the views at 37 // their old positions. 38 if (mTransientStateViews == null) { 39 mTransientStateViews = new SparseArray<View>(); 40  } 41  mTransientStateViews.put(position, scrap); 42 } else { 43 // Otherwise, we'll have to remove the view and start over. 44 if (mSkippedScrap == null) { 45 mSkippedScrap = new ArrayList<View>(); 46  } 47  mSkippedScrap.add(scrap); 48  } 49 } else { 50 if (mViewTypeCount == 1) { 51  mCurrentScrap.add(scrap); 52 } else { 53  mScrapViews[viewType].add(scrap); 54  } 55 56 // Clear any system-managed transient state. 57 if (scrap.isAccessibilityFocused()) { 58  scrap.clearAccessibilityFocus(); 59  } 60 61 scrap.setAccessibilityDelegate(null); 62 63 if (mRecyclerListener != null) { 64  mRecyclerListener.onMovedToScrapHeap(scrap); 65  } 66  } 67 }

 addScrapView 方法中,被回收的view会根据其类型加入 mScrapViews 中。

特别的,如果这个view处于TransientState(瞬态,view正在播放动画或其他情况),则会被存入 mTransientStateViewsById  mTransientStateViews 

 

5.3 从View回收站获取View

Adapter的getView方法在AbsListView的 obtainView 中被调用:

 1 View obtainView(int position, boolean[] isScrap) {  2 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");  3 isScrap[0] = false;  4  View scrapView;  5 scrapView = mRecycler.getTransientStateView(position);  6 if (scrapView == null) {  7 scrapView = mRecycler.getScrapView(position);  8  }  9 10  View child; 11 if (scrapView != null) { 12 child = mAdapter.getView(position, scrapView, this); 13 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 14  child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 15  } 16 if (child != scrapView) { 17  mRecycler.addScrapView(scrapView, position); 18 if (mCacheColorHint != 0) { 19  child.setDrawingCacheBackgroundColor(mCacheColorHint); 20  } 21 } else { 22 isScrap[0] = true; 23 // Clear any system-managed transient state so that we can 24 // recycle this view and bind it to different data. 25 if (child.isAccessibilityFocused()) { 26  child.clearAccessibilityFocus(); 27  } 28  child.dispatchFinishTemporaryDetach(); 29  } 30 } else { 31 child = mAdapter.getView(position, null, this); 32 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 33  child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 34  } 35 if (mCacheColorHint != 0) { 36  child.setDrawingCacheBackgroundColor(mCacheColorHint); 37  } 38  } 39 40 //... 41 42 return child; 43 }

可以看到,对于不处于TransientState的View,将会尝试通过 getScrapView 方法获取回收的View,如果有,就会作为参数传入Adatper的getView方法中。

 getScrapView 方法,其实就是先调用Adapter的 getItemViewType 方法取position对应的view类型,然后从 mScrapViews 中根据类型取view。

 1 View getScrapView(int position) {  2 if (mViewTypeCount == 1) {  3 return retrieveFromScrap(mCurrentScrap, position);  4 } else {  5 int whichScrap = mAdapter.getItemViewType(position);  6 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {  7 return retrieveFromScrap(mScrapViews[whichScrap], position);  8  }  9  } 10 return null; 11 }

至此,我们简要了解了多类型的listview中,是如何在滑动屏幕时回收view并进行重用的。

而如何维护每个类型item对应的View数组,以及TransientState的维护,本篇文章就不做详细介绍了,有兴趣的读者可以着重研究一下AbsListView的源码。

 

[转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154873.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • udp 视频传输_webrtc视频流传输

    udp 视频传输_webrtc视频流传输在UDP实时图像传输一文中,我们介绍了如何使用UDP来实现视频的实时传输,并使用C#进行了发送端和接收端的搭建。但是这篇文章中的方法是对整张图片进行JPEG压缩,并通过UDP一次性地发送到接收端,由于一个UDP数据包只能发送64k字节的数据,所以这篇文章中的方法的图片传输大小是有限制的,实测只能发送480P视频中的图像。所以在本文中,我们将继续采取逐帧发送的形式,以1080P的视频为例,实现更高清晰度(1080×1920×31080\times1920\times31080×1920×3)的图像实时传

  • Pycharm中出现ImportError:DLL load failed:找不到指定模块的解决方法

    Pycharm中出现ImportError:DLL load failed:找不到指定模块的解决方法关于conda安装matplotlib报错最近在师姐机器上跑实验的时候,想利用matplotlib包来绘制损失曲线图,安装过程中碰到了一些小麻烦,感觉之前好像也碰到过类似的问题,所以这次记录下来系统版本:Windows10python包管理工具:conda4.7.11python版本:3.7.4

  • 浅谈单工,半双工和全双工有何区别和联系?[通俗易懂]

    浅谈单工,半双工和全双工有何区别和联系?

  • 有空考个SCSA

    有空考个SCSA有空考个SCSA玩玩,反正也想学Solaris~不过培训太贵了,裸考算了~

  • PNG文件格式具体解释

    PNG文件格式具体解释

  • Java+Servlet+JSP+Mysql+Tomcat实现Web学生选课管理系统

    Java+Servlet+JSP+Mysql+Tomcat实现Web学生选课管理系统Java实现Web学生选课管理系统一、系统介绍1.软件环境2.系统功能3.数据库二、系统展示1.登录页面2.学生-主页面3.学生-查看个人信息4.学生-选择课程5.学生-查看已选课程6.教师-主页面7.教师-查看个人信息8.教师-评分9.教师-查看任课信息10.管理员-主页面11.管理员-管理员功能-查看个人信息12.管理员-管理员功能-添加新的管理员13.管理员-学生功能-添加学生14.管理员-学生功能-获取所有学生15.管理员-课程功能-添加课程16.管理员-课程功能-查询课程17.管理员-教师功能-添

    2022年10月16日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号