表情用于聊天对话的输入,实现的原理主要是:在EditText或TextView中,使用SpannableString,将特定字符串替换为图片。

首先,我们可以规定,表情的字符串为[**],图片名称为smiley_i.png(i为图片编号,从0开始)。

当我选择表情时,EditText的实际输入为[憨笑],但是可以通过SpannableString,将[憨笑]替换显示为。如下图,EditText的实际输入为“哈哈哈[憨笑][憨笑]”。

同样,将“哈哈哈[憨笑][憨笑]”在TextView显示,只需要将所有的表情字符串[**]替换为对应的表情图片即可。

注意:这里只有字符串--》表情的转换,不存在表情--》字符串的转换。

具体实现:

1. 表情字符串与图片资源的对应

表情字符串存储在arrays.xml的smiley_values数组中,其位置下标与对应表情的图片编号一致。比如[憨笑]的数组下标为40,则其表情图片名为smiley_40.png。然后可以根据smiley_40.png获得该图片的资源ID,如0x00000001。我们只要将[憨笑]和0x00000001一一对应,就可以实现表情的显示。

2. 自定义带表情输入的编辑框

FacialEditLayout.java:带表情输入的自定义编辑框布局。

public class FacialEditLayout extends LinearLayout implementsOnItemClickListener, OnClickListener {private Context mContext;/** 发送按钮的监听事件 */private OnSendBtnClickListener mSendBtnListener;/** 显示表情页的viewpager */private ViewPager mFacialViewPager;/** 表情页界面集合 */private ArrayList<View> mPageViews;/** 游标显示布局 */private LinearLayout mCursorLayout;/** 游标点集合 */private ArrayList<ImageView> mCursorViews;/** 表情集合 页+个 */private List<List<SmileyFacial>> smileys;/** 表情区域 */private View mFacialLayout;/** 打开或关闭表情的按钮 */private ImageView mFacialEnable;/** 输入框 */private EditText mMsgEdit;/** 发送按钮 */private Button mSendBtn;/** 表情数据填充器 */private List<FacialAdapter> mFacialAdapters;/** 当前表情页 */private int current = 0;/** 表情的列数 */private static final int COLUMNS = 7;/** 表情的行数 */private static final int ROWS = 3;/** 游标的宽高 */private static final int CURSOR_DIMEN = 12;/** 整个表情布局的高度,包含表情框,游标 */private int mFacialLayoutHeight;/** 表情ViewPager的高度 */private int mFacialViewPagerHeight;/** 每个表情的高度 */private int mFacialHeight;/** 每个表情的宽度 */private int mFacialWidth;public FacialEditLayout(Context context) {super(context);mContext = context;LayoutInflater.from(mContext).inflate(R.layout.facial_edit_layout, this);}public FacialEditLayout(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;LayoutInflater.from(mContext).inflate(R.layout.facial_edit_layout, this);}/***************************** 对外接口 start *********************************//** 发送按钮点击监听 */public interface OnSendBtnClickListener {/*** @param input*            编辑框的输入*/void onBtnClicked(String input);}/** 设置按钮点击监听器 */public void setOnSendBtnClickListener(OnSendBtnClickListener listener) {mSendBtnListener = listener;}/** 获得编辑框的输入字串 */public String getMsgEditTxt() {return mMsgEdit.getText().toString();}/** 清空编辑框 */public void clearMsgEdit() {mMsgEdit.setText("");}/*** 判断表情框是否显示<BR/>* 一般用于重写返回按键时调用*/public boolean isFacialShow() {return (mFacialLayout.getVisibility() == View.VISIBLE);}/*** 隐藏表情选择框<BR/>* 一般用于重写返回按键时调用*/public void hideFacialView() {if (mFacialLayout.getVisibility() == View.VISIBLE) {mFacialEnable.setImageDrawable(getResources().getDrawable(R.drawable.facial_btn_normal));mFacialLayout.setVisibility(View.GONE);}}/***************************** 对外接口 end *********************************/@Overrideprotected void onFinishInflate() {super.onFinishInflate();onCreate();}private void onCreate() {initView();initViewPager();initCursor();initData();}/*** 初始化控件*/private void initView() {mFacialEnable = (ImageView) findViewById(R.id.facial_enable);mMsgEdit = (EditText) findViewById(R.id.facial_edit);mSendBtn = (Button) findViewById(R.id.facial_sendBtn);mFacialLayout = findViewById(R.id.facial_facialLayout);mFacialViewPager = (ViewPager) findViewById(R.id.facial_viewPager);mCursorLayout = (LinearLayout) findViewById(R.id.facial_cursor);mMsgEdit.setOnClickListener(this);mFacialEnable.setOnClickListener(this);mSendBtn.setOnClickListener(this);// 获得屏幕宽度WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);int screenWidth = wm.getDefaultDisplay().getWidth();// 每个表情的宽高mFacialWidth = screenWidth / COLUMNS - 1;mFacialHeight = mFacialWidth;// 表情框高度mFacialViewPagerHeight = mFacialHeight * ROWS + 20;// 整个表情布局高度mFacialLayoutHeight = mFacialViewPagerHeight + CURSOR_DIMEN + 30;RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mFacialViewPager.getLayoutParams();lp.height = mFacialViewPagerHeight;mFacialViewPager.setLayoutParams(lp);LinearLayout.LayoutParams lp1 = (LayoutParams) mFacialLayout.getLayoutParams();lp1.height = mFacialLayoutHeight;mFacialLayout.setLayoutParams(lp1);smileys = FacialUtils.getInstace().getFacialData();}/*** 初始化显示表情的viewpager*/private void initViewPager() {mPageViews = new ArrayList<View>();// 左侧添加空页View nullView1 = new View(mContext);// 设置透明背景nullView1.setBackgroundColor(Color.TRANSPARENT);mPageViews.add(nullView1);// 中间添加表情页mFacialAdapters = new ArrayList<FacialAdapter>();for (int i = 0; i < smileys.size(); i++) {GridView view = new GridView(mContext);FacialAdapter adapter = new FacialAdapter(mContext, smileys.get(i),mFacialWidth, mFacialWidth);view.setAdapter(adapter);mFacialAdapters.add(adapter);view.setOnItemClickListener(this);view.setNumColumns(COLUMNS);// 列数view.setBackgroundColor(Color.TRANSPARENT);view.setHorizontalSpacing(1);view.setVerticalSpacing(1);view.setStretchMode(GridView.STRETCH_COLUMN_WIDTH);view.setCacheColorHint(0);view.setPadding(5, 0, 5, 0);view.setSelector(new ColorDrawable(Color.TRANSPARENT));view.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));view.setGravity(Gravity.CENTER);mPageViews.add(view);}// 右侧添加空页面View nullView2 = new View(mContext);// 设置透明背景nullView2.setBackgroundColor(Color.TRANSPARENT);mPageViews.add(nullView2);}/*** 初始化游标*/private void initCursor() {mCursorViews = new ArrayList<ImageView>();ImageView imageView;for (int i = 0; i < mPageViews.size(); i++) {imageView = new ImageView(mContext);imageView.setBackgroundResource(R.drawable.facial_cursor_normal);LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));layoutParams.leftMargin = 10;layoutParams.rightMargin = 10;layoutParams.width = CURSOR_DIMEN;layoutParams.height = CURSOR_DIMEN;mCursorLayout.addView(imageView, layoutParams);if (i == 0 || i == mPageViews.size() - 1) {imageView.setVisibility(View.GONE);}if (i == 1) {imageView.setBackgroundResource(R.drawable.facial_cursor_selected);}mCursorViews.add(imageView);}}/*** 填充数据*/private void initData() {mFacialViewPager.setAdapter(new ViewPagerAdapter(mPageViews));mFacialViewPager.setCurrentItem(1);current = 0;mFacialViewPager.setOnPageChangeListener(new OnPageChangeListener() {@Overridepublic void onPageSelected(int position) {current = position - 1;// 描绘分页点drawCursor(position);// 如果是第一屏或者是最后一屏禁止滑动,其实这里实现的是如果滑动的是第一屏则跳转至第二屏,如果是最后一屏则跳转到倒数第二屏.if (position == mCursorViews.size() - 1 || position == 0) {if (position == 0) {mFacialViewPager.setCurrentItem(position + 1);// 第二屏// 会再次实现该回调方法实现跳转.mCursorViews.get(1).setBackgroundResource(R.drawable.facial_cursor_selected);} else {mFacialViewPager.setCurrentItem(position - 1);// 倒数第二屏mCursorViews.get(position - 1).setBackgroundResource(R.drawable.facial_cursor_selected);}}}@Overridepublic void onPageScrolled(int arg0, float arg1, int arg2) {}@Overridepublic void onPageScrollStateChanged(int arg0) {}});}/*** 绘制游标背景*/private void drawCursor(int index) {for (int i = 1; i < mCursorViews.size(); i++) {if (index == i) {mCursorViews.get(i).setBackgroundResource(R.drawable.facial_cursor_selected);} else {mCursorViews.get(i).setBackgroundResource(R.drawable.facial_cursor_normal);}}}/** 隐藏软键盘 */private void hideKeyBoard() {if (mContext != null) {((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(mMsgEdit.getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);}}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.facial_enable:// 表情选择/** 让编辑框同时获得焦点 否则,如果先点击表情框,需要两次点击编辑框,表情框才会消失。* 因为第一次点击,让编辑框获得焦点,然后才能监听点击操作*/mMsgEdit.setFocusable(true);mMsgEdit.setFocusableInTouchMode(true);mMsgEdit.requestFocus();// 显示或隐藏表情选择框if (mFacialLayout.getVisibility() == View.VISIBLE) {mFacialEnable.setImageDrawable(getResources().getDrawable(R.drawable.facial_btn_normal));mFacialLayout.setVisibility(View.GONE);} else {mFacialEnable.setImageDrawable(getResources().getDrawable(R.drawable.facial_btn_enable));// 隐藏软键盘hideKeyBoard();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}mFacialLayout.setVisibility(View.VISIBLE);}break;case R.id.facial_edit:// 编辑框// 隐藏表情选择框if (mFacialLayout.getVisibility() == View.VISIBLE) {mFacialEnable.setImageDrawable(getResources().getDrawable(R.drawable.facial_btn_normal));mFacialLayout.setVisibility(View.GONE);}break;case R.id.facial_sendBtn:// 发送按钮String input = mMsgEdit.getText().toString();if (input.isEmpty())return;if (mSendBtnListener != null) {// 调用按钮监听mSendBtnListener.onBtnClicked(input);}break;}}/*** 监听表情选择*/@Overridepublic void onItemClick(AdapterView<?> arg0, View view, int position,long arg3) {SmileyFacial smiley = (SmileyFacial) mFacialAdapters.get(current).getItem(position);if (smiley.getResId() == R.drawable.facial_del_selector) {// 删除按钮int selection = mMsgEdit.getSelectionStart();String text = mMsgEdit.getText().toString();if (selection > 0) {String str = text.substring(selection - 1);if ("]".equals(str)) {// 光标以前的字符串中,最后一个是表情符int start = text.lastIndexOf("[");int end = selection;mMsgEdit.getText().delete(start, end);} else// 正常字符{mMsgEdit.getText().delete(selection - 1, selection);}}}if (!TextUtils.isEmpty(smiley.getName())) {// 选择表情SpannableString spannableString = FacialUtils.getInstace().addFacial(getContext(), smiley.getResId(),smiley.getName());// 将表情显示在编辑框中mMsgEdit.append(spannableString);}}
}

FacialUtil.java:初始化数据,和添加,显示表情的工具类

public class FacialUtils {/** 每一页表情的个数,不包含最后一个删除键 */private int pageSize = 20;private static FacialUtils mFacialUtil;/** <表情对应字符串,资源ID> 比如:<[哈哈],0x7f040000> */private HashMap<String, Integer> smileyMap = new HashMap<String, Integer>();/** 表情集合 */private List<SmileyFacial> smileys = new ArrayList<SmileyFacial>();/** 表情分页的结果集合 */private List<List<SmileyFacial>> smileyLists = new ArrayList<List<SmileyFacial>>();private FacialUtils() {}public static FacialUtils getInstace() {if (mFacialUtil == null) {mFacialUtil = new FacialUtils();}return mFacialUtil;}/*** 初始化表情和对应字符串*/public void InitFacialData(Context context) {// 得到所有表情的字符串String[] data = context.getResources().getStringArray(R.array.smiley_values);if (data == null) {return;}SmileyFacial smileyEentry;try {for (int i = 0; i < data.length; i++) {String pngName = "smiley_" + i;// 根据图片名获得资源IDint resID = context.getResources().getIdentifier(pngName,"drawable", context.getPackageName());smileyMap.put(data[i], resID);if (resID != 0) {smileyEentry = new SmileyFacial();smileyEentry.setResId(resID);smileyEentry.setName(data[i]);smileys.add(smileyEentry);}}// 表情页的页数,向上取整int pageCount = (int) Math.ceil(smileys.size() / pageSize + 0.1);for (int i = 0; i < pageCount; i++) {smileyLists.add(getPageFacials(i));}} catch (Exception e) {e.printStackTrace();}}/*** 获得分页后的表情集合*/public List<List<SmileyFacial>> getFacialData() {return smileyLists;}/*** 将字符串进行正则匹配,用于显示表情* * @param str*            含有[**]表情标识的字串* @return 可通过TextView或EditText的setText方法直接显示表情的SpannableString*/public SpannableString showFacial(Context context, String str) {SpannableString spannableString = new SpannableString(str);// 正则表达式比配字符串里是否含有表情,如: 我好[开心]啊String regex = "\\[[^\\]]+\\]";// 通过传入的正则表达式来生成一个patternPattern sinaPatten = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);try {dealExpression(context, spannableString, sinaPatten, 0);} catch (Exception e) {Log.e("dealExpression", e.getMessage());}return spannableString;}/*** 添加表情*/public SpannableString addFacial(Context context, int imgId, String str) {if (TextUtils.isEmpty(str)) {return null;}Drawable drawable = context.getResources().getDrawable(imgId);drawable.setBounds(0, 0, 30, 30);ImageSpan imageSpan = new ImageSpan(drawable);SpannableString spannable = new SpannableString(str);spannable.setSpan(imageSpan, 0, str.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);return spannable;}/*** 对spanableString进行正则判断,如果符合要求,则以表情图片代替*/private void dealExpression(Context context,SpannableString spannableString, Pattern patten, int start)throws Exception {Matcher matcher = patten.matcher(spannableString);while (matcher.find()) {String key = matcher.group();// 返回第一个字符的索引的文本匹配整个正则表达式,ture 则继续递归if (matcher.start() < start) {continue;}int resId = smileyMap.get(key);if (resId != 0) {Drawable drawable = context.getResources().getDrawable(resId);drawable.setBounds(0, 0, 30, 30);// 通过图片资源id来得到drawable,用一个ImageSpan来包装ImageSpan imageSpan = new ImageSpan(drawable);// 计算该图片名字的长度,也就是要替换的字符串的长度int end = matcher.start() + key.length();// 将该图片替换字符串中规定的位置中spannableString.setSpan(imageSpan, matcher.start(), end,Spannable.SPAN_INCLUSIVE_EXCLUSIVE);if (end < spannableString.length()) {// 如果整个字符串还未验证完,则继续。。dealExpression(context, spannableString, patten, end);}break;}}}/*** 获取分页数据*/private List<SmileyFacial> getPageFacials(int page) {int startIndex = page * pageSize;int endIndex = startIndex + pageSize;if (endIndex > smileys.size()) {endIndex = smileys.size();}List<SmileyFacial> list = new ArrayList<SmileyFacial>();list.addAll(smileys.subList(startIndex, endIndex));if (list.size() < pageSize) {// 填充空白区域for (int i = list.size(); i < pageSize; i++) {SmileyFacial object = new SmileyFacial();list.add(object);}}if (list.size() == pageSize) {// 最后一个为删除键SmileyFacial object = new SmileyFacial();object.setResId(R.drawable.facial_del_selector);list.add(object);}return list;}
}

facial_edit_layout.xml:自定义View的布局文件

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/facialEditLayout"android:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="vertical" ><RelativeLayoutandroid:id="@+id/facial_editLayout"android:layout_width="fill_parent"android:layout_height="wrap_content"android:background="@drawable/chatting_bottom_bg"android:padding="5dp" ><ImageViewandroid:id="@+id/facial_enable"android:layout_width="35dp"android:layout_height="35dp"android:layout_alignBottom="@+id/facial_edit"android:layout_alignParentLeft="true"android:padding="5dp"android:src="@drawable/facial_btn_normal" /><EditTextandroid:id="@+id/facial_edit"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_marginLeft="10dp"android:layout_marginRight="10dp"android:layout_toLeftOf="@+id/facial_sendBtn"android:layout_toRightOf="@+id/facial_enable"android:background="@drawable/facial_edit_selector"android:inputType="textMultiLine"android:maxLines="3"android:textSize="16sp" /><Buttonandroid:id="@+id/facial_sendBtn"android:layout_width="50dp"android:layout_height="35dp"android:layout_alignBottom="@+id/facial_edit"android:layout_alignParentRight="true"android:background="@drawable/facial_send_btn_selector"android:text="@string/btn_send" /></RelativeLayout><!-- 表情区域 --><RelativeLayoutandroid:id="@+id/facial_facialLayout"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_below="@id/facial_editLayout"android:visibility="gone"android:windowSoftInputMode="adjustPan" ><android.support.v4.view.ViewPagerandroid:id="@+id/facial_viewPager"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_above="@+id/facial_cursor"android:layout_marginTop="10dp" ></android.support.v4.view.ViewPager><LinearLayoutandroid:id="@+id/facial_cursor"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_marginBottom="10dp"android:layout_marginTop="5dp"android:gravity="center"android:orientation="horizontal" /></RelativeLayout></LinearLayout>

3. 表情输入框的使用
   在聊天界面中,需要使用表情输入框。使用比较简单,只要实现发送按钮的点击函数(setOnSendBtnClickListener)即可。同时,可以使用getMsgEditTxt获得消息内容,clearMsgEdit清空文本,isFacialShow判断表情框是否显示,以及hideFacialView隐藏表情框。

ChattingActivity.java:聊天界面Activity。主要贴出使用表情输入框的代码。

public class ChattingActivity extends ActionBarActivity{private ListView mListView;private FacialEditLayout mFacialEditLayout;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_chatting);findViews();setListener();}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if(keyCode == KeyEvent.KEYCODE_BACK){if(mFacialEditLayout.isFacialShow()){//隐藏表情框mFacialEditLayout.hideFacialView();return true;}}return super.onKeyDown(keyCode, event);}private void setListener(){//点击聊天消息的界面,隐藏键盘或表情框mListView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {hideKeyBoard();mFacialEditLayout.hideFacialView();return false;}});//设置发送按钮点击监听mFacialEditLayout.setOnSendBtnClickListener(new FacialEditLayout.OnSendBtnClickListener() {@Overridepublic void onBtnClicked(String input) {//清空编辑框mFacialEditLayout.clearMsgEdit();//发送消息}});}private void findViews(){mListView = (ListView) findViewById(R.id.chatting_listView);mFacialEditLayout = (FacialEditLayout) findViewById(R.id.chatting_facialLayout);}/** 隐藏软键盘 */private void hideKeyBoard() {((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);}
}

参考网址:http://blog.csdn.net/lnb333666/article/details/8546497

首页Android聊天软件的开发

Android聊天软件的开发(六)--表情相关推荐

  1. Android聊天软件的开发(七)--聊天通信

    聊天通信通过Socket实现,大概的框架如下图: 通信流程: 1.服务器在启动时开启聊天服务线程 可以通过ServletContextListener监听Servlet的初始化和销毁,来开启和关闭聊天 ...

  2. Android聊天软件的开发--聊天通信

    Android聊天软件的开发(七)--聊天通信 2014-06-20 23:17:49CSDN-vaintwyt-点击数:338  聊天通信通过Socket实现,大概的框架如下图: 通信流程: 1.服 ...

  3. Android聊天软件的开发(二)--数据库

    一,服务器数据库    服务器端的数据库是MySQL,使用Hibernate实现数据的增删改查.主要存储的数据有:用户信息,好友列表.             其中,好友列表中的friend_list ...

  4. Android聊天软件的开发(四)--通讯录

    一,好友排序    好友排序是按照昵称拼音进行A-Z排序.效果如下图:      对好友昵称进行排序,需要先将首字转换为ASCII码,然后根据ASCII码得到对应的拼音,最后根据拼音进行A-Z排序.点 ...

  5. Android聊天软件的开发(三)--网络连接

    一,服务器网络接口    服务器网络接口通过Servlet实现,可以获得客户端提交的数据,对数据进行查询存储操作,以及返回结果数据给客户端.客户端可以通过HTTP协议直接访问网络接口.    HTTP ...

  6. Android聊天软件界面开发

    聊天软件界面开发 前言:           这是开始学习Android的开发的第5天,一直是跟着郭霖大师的第一行代码学习,              这里边发篇博文记录,边帮自己整理下思路,毕竟思路 ...

  7. 开发简单Android聊天软件(1)

    总体介绍 开篇 大概思路 一. 客户端主要依赖 二.包引用完成后,创建wsClient类. 三.连接成功后,就可以在对于业务逻辑调用以下方法开始发送消息 开篇 本人是一位开发新人,将自己的开发学习过程 ...

  8. 开发简单Android聊天软件(7)

    构建离线消息获取流程 在 "开发简单Android聊天软件(6)" 中,完成了完成消息接收和加载,构建一个完整的聊天流程. 但是我们只完成了一半,完成存量历史记录展示,和即时聊天的 ...

  9. 开发简单Android聊天软件(6)

    构建完整消息接收加载流程 在 "开发简单Android聊天软件(5)" 中,完成了会话窗口的绘制,以及消息发送.现在我们来完成消息接收和加载,构建一个完整的聊天流程. 消息加载,那 ...

最新文章

  1. 半导体并购停不下来 ADI拟148亿美元收购Linear
  2. UA MATH564 概率论V 中心极限定理
  3. matlab机械臂工作空间代码_【ROS-Moveit!】机械臂控制探索(3)——基于python的API示例代码分析...
  4. C#用注册表开机自动启动某某软件
  5. 通才还是专才——由摩托裁员引发的讨论
  6. 我是WPF菜鸟之(4)---关于XAML与逻辑代码
  7. tlwr840n虚拟服务器,TP-Link TL-WR840N 300M无线路由器设置
  8. 用户界面组件-菜单(Menus)
  9. Parcel打包React
  10. 傅里叶级数 三角形式 到 复数形式
  11. 国内外云服务现状及发展探讨
  12. 德语语法笔记——动词的变位
  13. Android集成FFmpeg并实现视频转码
  14. 模电学习笔记(上交郑老师)1.PN结
  15. Android Vold 架构简析
  16. python编辑程序模型_用Python的SimPy库简化复杂的编程模型的介绍
  17. docker/Dockerfile/docker compose
  18. PMP每日⑤题(七)
  19. 计算机仿真的主要目的,数控加工仿真的主要目的是什么?
  20. Android-如何设置APP开机启动(图文)

热门文章

  1. 虚幻3(导演动画)四机器人(骨架网格物体)动画化
  2. mysql chinanet外网连接不上_中国的chinanet计算机网络是什么
  3. 移动开发:学习手机网站设计和制作的25个优秀案例
  4. C++(34)——收集瓶盖赢大奖
  5. 我用Python做设计(一)
  6. 刀锋开路 IBM云体验展示解决方案
  7. uniapp获取元素的宽度、高度
  8. word文件不小心被删怎么恢复
  9. 表单序列化serialize()与serializeArray()的使用及字符串转换
  10. php 查看类的方法,php如何查看类的方法