环信UI开源Demo情景分析九、聊天界面(一)

2023-10-30 04:51

本文主要是介绍环信UI开源Demo情景分析九、聊天界面(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

从会话列表点击一个会话进入聊天界面,现在咱们先来看看聊天界面的清单配置。(在代码中注释比较多,有兴趣的童雪不要只关注文本)

        <!-- 聊天页面 --><activityandroid:name=".activity.ChatActivity"android:launchMode="singleTop"android:screenOrientation="portrait"android:theme="@style/horizontal_slide"android:windowSoftInputMode="adjustResize" ></activity>
adjustResize:该Activity总是调整屏幕的大小以便留出软键盘的空间。

界面布局相对复杂一点,其中在ListVIew上面还有一个语音提示的布局。

<RelativeLayoutandroid:id="@+id/recording_container"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/recording_hint_bg"android:padding="10dp"android:visibility="invisible" ><ImageViewandroid:id="@+id/mic_image"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:src="@drawable/record_animate_01" /><TextViewandroid:id="@+id/recording_hint"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/mic_image"android:layout_centerHorizontal="true"android:layout_marginTop="5dp"android:padding="2dp"android:text="@string/move_up_to_cancel"android:textSize="10sp" /></RelativeLayout>
并且在输入框下面有一个ViewPager用来显示表情图标,与之同样的还有可以输入的图片,表情,名片等按钮。在这里对布局文件就不详细说明了。

/*** initView*/protected void initView() {//录音布局recordingContainer = findViewById(R.id.recording_container);//录音图标micImage = (ImageView) findViewById(R.id.mic_image);//录音说明recordingHint = (TextView) findViewById(R.id.recording_hint);//聊天列表listView = (ListView) findViewById(R.id.list);//com.easemob.chatuidemo.widget.PasteEditText 输入框mEditTextContent = (PasteEditText) findViewById(R.id.et_sendmessage);//设置输入类型 当前为键盘图标buttonSetModeKeyboard = findViewById(R.id.btn_set_mode_keyboard);//底部布局edittext_layout = (RelativeLayout) findViewById(R.id.edittext_layout);//设置输入类型 当前为声音图标buttonSetModeVoice = findViewById(R.id.btn_set_mode_voice);//发送按钮buttonSend = findViewById(R.id.btn_send);//按下说话按钮buttonPressToSpeak = findViewById(R.id.btn_press_to_speak);//显示表情ViewpagerexpressionViewpager = (ViewPager) findViewById(R.id.vPager);//表情图片emojiIconContainer = (LinearLayout) findViewById(R.id.ll_face_container);//图片,表情,名片等按钮btnContainer = (LinearLayout) findViewById(R.id.ll_btn_container);//位置locationImgview = (ImageView) findViewById(R.id.btn_location);//emoji图标默认iv_emoticons_normal = (ImageView) findViewById(R.id.iv_emoticons_normal);//emoji图标 选择iv_emoticons_checked = (ImageView) findViewById(R.id.iv_emoticons_checked);//加载loadmorePB = (ProgressBar) findViewById(R.id.pb_load_more);//更多 加号图标btnMore = (Button) findViewById(R.id.btn_more);//设置默认图标状态iv_emoticons_normal.setVisibility(View.VISIBLE);iv_emoticons_checked.setVisibility(View.INVISIBLE);//更多  默认视图more = findViewById(R.id.more);//输入框默认背景edittext_layout.setBackgroundResource(R.drawable.input_bar_bg_normal);// 动画资源文件,用于录制语音时micImages = new Drawable[] { //getResources().getDrawable(R.drawable.record_animate_01), //getResources().getDrawable(R.drawable.record_animate_02), //getResources().getDrawable(R.drawable.record_animate_03), //getResources().getDrawable(R.drawable.record_animate_04), //getResources().getDrawable(R.drawable.record_animate_05), //getResources().getDrawable(R.drawable.record_animate_06), //getResources().getDrawable(R.drawable.record_animate_07), //getResources().getDrawable(R.drawable.record_animate_08), //getResources().getDrawable(R.drawable.record_animate_09), //getResources().getDrawable(R.drawable.record_animate_10), //getResources().getDrawable(R.drawable.record_animate_11), //getResources().getDrawable(R.drawable.record_animate_12), //getResources().getDrawable(R.drawable.record_animate_13), //getResources().getDrawable(R.drawable.record_animate_14), };// 表情listreslist = getExpressionRes(35);// 初始化表情viewpagerList<View> views = new ArrayList<View>();View gv1 = getGridChildView(1);View gv2 = getGridChildView(2);views.add(gv1);views.add(gv2);expressionViewpager.setAdapter(new ExpressionPagerAdapter(views));edittext_layout.requestFocus();voiceRecorder = new VoiceRecorder(micImageHandler);buttonPressToSpeak.setOnTouchListener(new PressToSpeakListen());mEditTextContent.setOnFocusChangeListener(new OnFocusChangeListener() {@Overridepublic void onFocusChange(View v, boolean hasFocus) {if (hasFocus) {edittext_layout.setBackgroundResource(R.drawable.input_bar_bg_active);} else {edittext_layout.setBackgroundResource(R.drawable.input_bar_bg_normal);}}});mEditTextContent.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {edittext_layout.setBackgroundResource(R.drawable.input_bar_bg_active);more.setVisibility(View.GONE);iv_emoticons_normal.setVisibility(View.VISIBLE);iv_emoticons_checked.setVisibility(View.INVISIBLE);emojiIconContainer.setVisibility(View.GONE);btnContainer.setVisibility(View.GONE);}});// 监听文字框mEditTextContent.addTextChangedListener(new TextWatcher() {@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {if (!TextUtils.isEmpty(s)) {btnMore.setVisibility(View.GONE);buttonSend.setVisibility(View.VISIBLE);} else {btnMore.setVisibility(View.VISIBLE);buttonSend.setVisibility(View.GONE);}}@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic void afterTextChanged(Editable s) {}});}
在这个初始化方法中,先赋值了一些控件按钮,加载录音时候的动画图片。将表情加载到ViewPager中。
并且动态监听输入框的变换,及时更改表情,发送等控件的状态。

    private void setUpView() {//表情点击事件iv_emoticons_normal.setOnClickListener(this);iv_emoticons_checked.setOnClickListener(this);// position = getIntent().getIntExtra("position", -1);//剪切板clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);//隐藏键盘getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);//屏幕睡眠时候关闭软键盘wakeLock = ((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "demo");// 判断单聊还是群聊chatType = getIntent().getIntExtra("chatType", CHATTYPE_SINGLE);if (chatType == CHATTYPE_SINGLE) { // 单聊toChatUsername = getIntent().getStringExtra("userId");((TextView) findViewById(R.id.name)).setText(toChatUsername);// conversation =// EMChatManager.getInstance().getConversation(toChatUsername,false);} else {// 群聊findViewById(R.id.container_to_group).setVisibility(View.VISIBLE);//删除聊天记录findViewById(R.id.container_remove).setVisibility(View.GONE);//声音findViewById(R.id.container_voice_call).setVisibility(View.GONE);//视频findViewById(R.id.container_video_call).setVisibility(View.GONE);toChatUsername = getIntent().getStringExtra("groupId");group = EMGroupManager.getInstance().getGroup(toChatUsername);//设置Group nameif (group != null)((TextView) findViewById(R.id.name)).setText(group.getGroupName());else((TextView) findViewById(R.id.name)).setText(toChatUsername);// conversation =// EMChatManager.getInstance().getConversation(toChatUsername,true);}conversation = EMChatManager.getInstance().getConversation(toChatUsername);// 把此会话的未读数置为0conversation.resetUnreadMsgCount();//加载消息// 初始化db时,每个conversation加载数目是getChatOptions().getNumberOfMessagesLoaded// 这个数目如果比用户期望进入会话界面时显示的个数不一样,就多加载一些final List<EMMessage> msgs = conversation.getAllMessages();int msgCount = msgs != null ? msgs.size() : 0;//pagesize = 20if (msgCount < conversation.getAllMsgCount() && msgCount < pagesize) {String msgId = null;if (msgs != null && msgs.size() > 0) {msgId = msgs.get(0).getMsgId();}if (chatType == CHATTYPE_SINGLE) {conversation.loadMoreMsgFromDB(msgId, pagesize);} else {conversation.loadMoreGroupMsgFromDB(msgId, pagesize);}}adapter = new MessageAdapter(this, toChatUsername, chatType);// 显示消息 并设置listview的一些事件listView.setAdapter(adapter);listView.setOnScrollListener(new ListScrollListener());adapter.refreshSelectLast();listView.setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {//当触摸listview区域时,隐藏键盘、表情hideKeyboard();more.setVisibility(View.GONE);iv_emoticons_normal.setVisibility(View.VISIBLE);iv_emoticons_checked.setVisibility(View.INVISIBLE);emojiIconContainer.setVisibility(View.GONE);btnContainer.setVisibility(View.GONE);return false;}});// 监听当前会话的群聊解散被T事件groupListener = new GroupListener();EMGroupManager.getInstance().addGroupChangeListener(groupListener);// show forward message if the message is not nullString forward_msg_id = getIntent().getStringExtra("forward_msg_id");if (forward_msg_id != null) {// 显示发送要转发的消息forwardMessage(forward_msg_id);}}

该方法主要是对聊天内容区的一些设置,通过对聊天类型的判断从而设置Name,在聊天区域被触摸的时候更改输入栏。监听群T事件以及获得转发内容。

接下来看一下点击事件。

/*** 消息图标点击事件* * @param view*/@Overridepublic void onClick(View view) {String st1 = getResources().getString(R.string.not_connect_to_server);int id = view.getId();if (id == R.id.btn_send) {// 点击发送按钮(发文字和表情)String s = mEditTextContent.getText().toString();sendText(s);} else if (id == R.id.btn_take_picture) {selectPicFromCamera();// 点击照相图标} else if (id == R.id.btn_picture) {selectPicFromLocal(); // 点击图片图标} else if (id == R.id.btn_location) { // 位置startActivityForResult(new Intent(this, BaiduMapActivity.class), REQUEST_CODE_MAP);} else if (id == R.id.iv_emoticons_normal) { // 点击显示表情框more.setVisibility(View.VISIBLE);iv_emoticons_normal.setVisibility(View.INVISIBLE);iv_emoticons_checked.setVisibility(View.VISIBLE);btnContainer.setVisibility(View.GONE);emojiIconContainer.setVisibility(View.VISIBLE);hideKeyboard();} else if (id == R.id.iv_emoticons_checked) { // 点击隐藏表情框iv_emoticons_normal.setVisibility(View.VISIBLE);iv_emoticons_checked.setVisibility(View.INVISIBLE);btnContainer.setVisibility(View.VISIBLE);emojiIconContainer.setVisibility(View.GONE);more.setVisibility(View.GONE);} else if (id == R.id.btn_video) {// 点击摄像图标Intent intent = new Intent(ChatActivity.this, ImageGridActivity.class);startActivityForResult(intent, REQUEST_CODE_SELECT_VIDEO);} else if (id == R.id.btn_file) { // 点击文件图标selectFileFromLocal();} else if (id == R.id.btn_voice_call) { // 点击语音电话图标if (!EMChatManager.getInstance().isConnected())Toast.makeText(this, st1, 0).show();elsestartActivity(new Intent(ChatActivity.this, VoiceCallActivity.class).putExtra("username", toChatUsername).putExtra("isComingCall", false));} else if (id == R.id.btn_video_call) { // 视频通话if (!EMChatManager.getInstance().isConnected())Toast.makeText(this, st1, 0).show();elsestartActivity(new Intent(this, VideoCallActivity.class).putExtra("username", toChatUsername).putExtra("isComingCall", false));}}
点击发送按钮(发文字和表情)sendText:

	/*** 发送文本消息* * @param content*            message content* @param isResend*            boolean resend*/private void sendText(String content) {if (content.length() > 0) {EMMessage message = EMMessage.createSendMessage(EMMessage.Type.TXT);// 如果是群聊,设置chattype,默认是单聊if (chatType == CHATTYPE_GROUP)message.setChatType(ChatType.GroupChat);TextMessageBody txtBody = new TextMessageBody(content);// 设置消息bodymessage.addBody(txtBody);// 设置要发给谁,用户username或者群聊groupidmessage.setReceipt(toChatUsername);// 把messgage加到conversation中conversation.addMessage(message);// 通知adapter有消息变动,adapter会根据加入的这条message显示消息和调用sdk的发送方法adapter.refreshSelectLast();mEditTextContent.setText("");setResult(RESULT_OK);}}
通过SDK发送消息,将消息加入到EMMessage中,发送之后刷新ListView,并且重置输入框。
照相selectPicFromCamera:

/*** 照相获取图片*/public void selectPicFromCamera() {if (!CommonUtils.isExitsSdcard()) {String st = getResources().getString(R.string.sd_card_does_not_exist);Toast.makeText(getApplicationContext(), st, 0).show();return;}cameraFile = new File(PathUtil.getInstance().getImagePath(), DemoApplication.getInstance().getUserName() + System.currentTimeMillis() + ".jpg");cameraFile.getParentFile().mkdirs();startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(cameraFile)), REQUEST_CODE_CAMERA);}
首先判断本地是否有存储卡,格式话生成的图片名称,最后调用系统相机进行拍照。

发送本地图片selectPicFromLocal:

/*** 从图库获取图片*/public void selectPicFromLocal() {Intent intent;if (Build.VERSION.SDK_INT < 19) {intent = new Intent(Intent.ACTION_GET_CONTENT);intent.setType("image/*");} else {intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);}startActivityForResult(intent, REQUEST_CODE_LOCAL);}
首先判断系统版本,如果小于19的话查找image下图片,否则直接通过系统提供的provider进行查找。

当点击位置的时候,调用百度地图的SDK(此处不详细说明)。

点击表情按钮的时候,重置more将其更改为表情布局,并且隐藏键盘。

more.setVisibility(View.VISIBLE);iv_emoticons_normal.setVisibility(View.INVISIBLE);iv_emoticons_checked.setVisibility(View.VISIBLE);btnContainer.setVisibility(View.GONE);emojiIconContainer.setVisibility(View.VISIBLE);hideKeyboard();
当继续点击表情按钮时,隐藏表情栏:

iv_emoticons_normal.setVisibility(View.VISIBLE);iv_emoticons_checked.setVisibility(View.INVISIBLE);btnContainer.setVisibility(View.VISIBLE);emojiIconContainer.setVisibility(View.GONE);more.setVisibility(View.GONE);
点击文件按钮selectFileFromLocal:

	/*** 选择文件*/private void selectFileFromLocal() {Intent intent = null;if (Build.VERSION.SDK_INT < 19) {intent = new Intent(Intent.ACTION_GET_CONTENT);intent.setType("*/*");intent.addCategory(Intent.CATEGORY_OPENABLE);} else {intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);}startActivityForResult(intent, REQUEST_CODE_SELECT_FILE);}
同发送照片。

当点击摄像图标的时候,启动一个界面ImageGridActivity:

public class ImageGridActivity extends FragmentActivity {private static final String TAG = "ImageGridActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {
//        if (BuildConfig.DEBUG) {
//            Utils.enableStrictMode();
//        }super.onCreate(savedInstanceState);if (getSupportFragmentManager().findFragmentByTag(TAG) == null) {final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();ft.add(android.R.id.content, new ImageGridFragment(), TAG);ft.commit();}}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);}
}
  <!-- choose video --><activityandroid:name=".activity.ImageGridActivity"android:screenOrientation="portrait"android:theme="@style/horizontal_slide"android:windowSoftInputMode="stateAlwaysHidden" ></activity>
其中内容是用ImageGridFragment来显示的。可以看到的网格界面使用GridView来显示的:

<GridView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/gridView"android:drawSelectorOnTop="true"android:listSelector="@drawable/photogrid_list_selector"android:layout_width="fill_parent"android:layout_height="fill_parent"android:columnWidth="@dimen/image_thumbnail_size"android:horizontalSpacing="@dimen/image_thumbnail_spacing"android:numColumns="auto_fit"android:stretchMode="columnWidth"android:verticalSpacing="@dimen/image_thumbnail_spacing" >
</GridView>

接下来我们来看一下ImageGridFragment里面到底是怎么设置的。

/*** Empty constructor as per the Fragment documentation*/public ImageGridFragment() {}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mImageThumbSize = getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size);mImageThumbSpacing = getResources().getDimensionPixelSize(R.dimen.image_thumbnail_spacing);mList = new ArrayList<VideoEntity>();getVideoFile();mAdapter = new ImageAdapter(getActivity());ImageCache.ImageCacheParams cacheParams = new ImageCache.ImageCacheParams();cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of// app memory// The ImageFetcher takes care of loading images into our ImageView// children asynchronouslymImageResizer = new ImageResizer(getActivity(), mImageThumbSize);mImageResizer.setLoadingImage(R.drawable.empty_photo);mImageResizer.addImageCache(getActivity().getSupportFragmentManager(), cacheParams);}

首先是一个空的构造方法,接下来在onCreate方法中获取设置的布局尺寸,其中有一点需要注意下:

getDimension和getDimensionPixelOffset的功能类似,

都是获取某个dimen的值,但是如果单位是dp或sp,则需要将其乘以density

如果是px,则不乘。并且getDimension返回float,getDimensionPixelOffset返回int.

而getDimensionPixelSize则不管写的是dp还是sp还是px,都会乘以denstiy.

然后定义了一个数组,类型为VideoEntity.

public class VideoEntity {public int ID;public String title;public String filePath;public int size;public int duration;
}
这是一个视频描述类,接下来调用方法获取文件:

	private void getVideoFile() {ContentResolver mContentResolver = getActivity().getContentResolver();Cursor cursor = mContentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Video.DEFAULT_SORT_ORDER);if (cursor != null && cursor.moveToFirst()) {do {// ID:MediaStore.Audio.Media._IDint id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID));// 名称:MediaStore.Audio.Media.TITLEString title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE));// 路径:MediaStore.Audio.Media.DATAString url = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));// 总播放时长:MediaStore.Audio.Media.DURATIONint duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));// 大小:MediaStore.Audio.Media.SIZEint size = (int) cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE));VideoEntity entty = new VideoEntity();entty.ID = id;entty.title = title;entty.filePath = url;entty.duration = duration;entty.size = size;mList.add(entty);} while (cursor.moveToNext());}if (cursor != null) {cursor.close();cursor = null;}}
此处的查询是查询系统里面自动记录的文件信息。

当获取到内容后通过Adapter显示出来:

private class ImageAdapter extends BaseAdapter {private final Context mContext;private int mItemHeight = 0;private RelativeLayout.LayoutParams mImageViewLayoutParams;public ImageAdapter(Context context) {super();mContext = context;mImageViewLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);}@Overridepublic int getCount() {return mList.size() + 1;}@Overridepublic Object getItem(int position) {return (position == 0) ? null : mList.get(position - 1);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup container) {ViewHolder holder = null;if (convertView == null) {holder = new ViewHolder();convertView = LayoutInflater.from(mContext).inflate(R.layout.choose_griditem, container, false);holder.imageView = (RecyclingImageView) convertView.findViewById(R.id.imageView);holder.icon = (ImageView) convertView.findViewById(R.id.video_icon);holder.tvDur = (TextView) convertView.findViewById(R.id.chatting_length_iv);holder.tvSize = (TextView) convertView.findViewById(R.id.chatting_size_iv);holder.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);holder.imageView.setLayoutParams(mImageViewLayoutParams);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}// Check the height matches our calculated column widthif (holder.imageView.getLayoutParams().height != mItemHeight) {holder.imageView.setLayoutParams(mImageViewLayoutParams);}// Finally load the image asynchronously into the ImageView, this// also takes care of// setting a placeholder image while the background thread runsString st1 = getResources().getString(R.string.Video_footage);if (position == 0) {holder.icon.setVisibility(View.GONE);holder.tvDur.setVisibility(View.GONE);holder.tvSize.setText(st1);holder.imageView.setImageResource(R.drawable.actionbar_camera_icon);} else {holder.icon.setVisibility(View.VISIBLE);VideoEntity entty = mList.get(position - 1);holder.tvDur.setVisibility(View.VISIBLE);holder.tvDur.setText(DateUtils.toTime(entty.duration));holder.tvSize.setText(TextFormater.getDataSize(entty.size));holder.imageView.setImageResource(R.drawable.empty_photo);mImageResizer.loadImage(entty.filePath, holder.imageView);}return convertView;// END_INCLUDE(load_gridview_item)}/*** Sets the item height. Useful for when we know the column width so the* height can be set to match.* * @param height*/public void setItemHeight(int height) {if (height == mItemHeight) {return;}mItemHeight = height;mImageViewLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, mItemHeight);mImageResizer.setImageSize(height);notifyDataSetChanged();}class ViewHolder {RecyclingImageView imageView;ImageView icon;TextView tvDur;TextView tvSize;}}
其中mList为全局变量,由于显示的视图里面第一项是拍摄视频,所以count要+1,getItem中position需要-1。接下来设置缓存。是通过自己写的缓存类在设置的:

/*** A holder class that contains cache parameters.*/public static class ImageCacheParams {public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;public int compressQuality = DEFAULT_COMPRESS_QUALITY;public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;/*** Sets the memory cache size based on a percentage of the max available* VM memory. Eg. setting percent to 0.2 would set the memory cache to* one fifth of the available memory. Throws* {@link IllegalArgumentException} if percent is < 0.01 or > .8.* memCacheSize is stored in kilobytes instead of bytes as this will* eventually be passed to construct a LruCache which takes an int in* its constructor.* * This value should be chosen carefully based on a number of factors* Refer to the corresponding Android Training class for more* discussion: http://developer.android.com/training/displaying-bitmaps/* * @param percent*            Percent of available app memory to use to size memory*            cache*/public void setMemCacheSizePercent(float percent) {if (percent < 0.01f || percent > 0.8f) {throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "+ "between 0.01 and 0.8 (inclusive)");}memCacheSize = Math.round(percent* Runtime.getRuntime().maxMemory() / 1024);}}

在onCreate方法中获取到数据,接下来在onCreateView中设置布局。

	@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {final View v = inflater.inflate(R.layout.image_grid_fragment, container, false);final GridView mGridView = (GridView) v.findViewById(R.id.gridView);mGridView.setAdapter(mAdapter);mGridView.setOnItemClickListener(this);mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {@Overridepublic void onScrollStateChanged(AbsListView absListView, int scrollState) {// Pause fetcher to ensure smoother scrolling when flingingif (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {// Before Honeycomb pause image loading on scroll to help// with performanceif (!Utils.hasHoneycomb()) {mImageResizer.setPauseWork(true);}} else {mImageResizer.setPauseWork(false);}}@Overridepublic void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}});// This listener is used to get the final width of the GridView and then// calculate the// number of columns and the width of each column. The width of each// column is variable// as the GridView has stretchMode=columnWidth. The column width is used// to set the height// of each view so we get nice square thumbnails.mGridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@TargetApi(VERSION_CODES.JELLY_BEAN)@Overridepublic void onGlobalLayout() {final int numColumns = (int) Math.floor(mGridView.getWidth() / (mImageThumbSize + mImageThumbSpacing));if (numColumns > 0) {final int columnWidth = (mGridView.getWidth() / numColumns) - mImageThumbSpacing;mAdapter.setItemHeight(columnWidth);if (BuildConfig.DEBUG) {Log.d(TAG, "onCreateView - numColumns set to " + numColumns);}if (Utils.hasJellyBean()) {mGridView.getViewTreeObserver().removeOnGlobalLayoutListener(this);} else {mGridView.getViewTreeObserver().removeGlobalOnLayoutListener(this);}}}});return v;}

其中通过暂停显示视频来确保平滑滚动。

有时候需要在onCreate方法中知道某个View组件的宽度和高度等信息,而直接调用View组件的getWidth()、getHeight()、getMeasuredWidth()、getMeasuredHeight()、getTop()、getLeft()等方法是无法获取到真实值的,只会得到0。这是因为View组件布局要在onResume回调后完成。下面提供实现方法,onGlobalLayout回调会在view布局完成时自动调用。

点击事件:

@Overridepublic void onItemClick(AdapterView<?> parent, View v, final int position, long id) {mImageResizer.setPauseWork(true);if (position == 0) {Intent intent = new Intent();intent.setClass(getActivity(), RecorderVideoActivity.class);startActivityForResult(intent, 100);} else {VideoEntity vEntty = mList.get(position - 1);// 限制大小不能超过10Mif (vEntty.size > 1024 * 1024 * 10) {String st = getResources().getString(R.string.temporary_does_not);Toast.makeText(getActivity(), st, Toast.LENGTH_SHORT).show();return;}Intent intent = getActivity().getIntent().putExtra("path", vEntty.filePath).putExtra("dur", vEntty.duration);getActivity().setResult(Activity.RESULT_OK, intent);getActivity().finish();}}
如果点击第一个,则开始录像,点击其他的视频则将视频的路径还有时长传递过去。

其中还有一个RecorderVideoActivity用来录像,此功能在这里不详细分析,有兴趣的同学可以自己去学习以下。

@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == Activity.RESULT_OK) {if (requestCode == 100) {Uri uri = data.getParcelableExtra("uri");String[] projects = new String[] { MediaStore.Video.Media.DATA, MediaStore.Video.Media.DURATION };Cursor cursor = getActivity().getContentResolver().query(uri, projects, null, null, null);int duration = 0;String filePath = null;if (cursor.moveToFirst()) {// 路径:MediaStore.Audio.Media.DATAfilePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));// 总播放时长:MediaStore.Audio.Media.DURATIONduration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));System.out.println("duration:" + duration);}if (cursor != null) {cursor.close();cursor = null;}getActivity().setResult(Activity.RESULT_OK, getActivity().getIntent().putExtra("path", filePath).putExtra("dur", duration));getActivity().finish();}}}

录像之后返回此界面,获取地址和时长后返回聊天界面。

	@Overridepublic void onResume() {super.onResume();mImageResizer.setExitTasksEarly(false);mAdapter.notifyDataSetChanged();}@Overridepublic void onDestroy() {super.onDestroy();mImageResizer.closeCache();mImageResizer.clearCache();}

还有两个发方法对数据更新及清除缓存。

再次回到聊天界面后调用onActivityResult方法:

/*** onActivityResult*/protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_CODE_EXIT_GROUP) {setResult(RESULT_OK);finish();return;}if (requestCode == REQUEST_CODE_CONTEXT_MENU) {switch (resultCode) {case RESULT_CODE_COPY: // 复制消息EMMessage copyMsg = ((EMMessage) adapter.getItem(data.getIntExtra("position", -1)));// clipboard.setText(SmileUtils.getSmiledText(ChatActivity.this,// ((TextMessageBody) copyMsg.getBody()).getMessage()));clipboard.setText(((TextMessageBody) copyMsg.getBody()).getMessage());break;case RESULT_CODE_DELETE: // 删除消息EMMessage deleteMsg = (EMMessage) adapter.getItem(data.getIntExtra("position", -1));conversation.removeMessage(deleteMsg.getMsgId());adapter.refreshSeekTo(data.getIntExtra("position", adapter.getCount()) - 1);break;case RESULT_CODE_FORWARD: // 转发消息EMMessage forwardMsg = (EMMessage) adapter.getItem(data.getIntExtra("position", 0));Intent intent = new Intent(this, ForwardMessageActivity.class);intent.putExtra("forward_msg_id", forwardMsg.getMsgId());startActivity(intent);break;default:break;}}if (resultCode == RESULT_OK) { // 清空消息if (requestCode == REQUEST_CODE_EMPTY_HISTORY) {// 清空会话EMChatManager.getInstance().clearConversation(toChatUsername);adapter.refresh();} else if (requestCode == REQUEST_CODE_CAMERA) { // 发送照片if (cameraFile != null && cameraFile.exists())sendPicture(cameraFile.getAbsolutePath());} else if (requestCode == REQUEST_CODE_SELECT_VIDEO) { // 发送本地选择的视频int duration = data.getIntExtra("dur", 0);String videoPath = data.getStringExtra("path");File file = new File(PathUtil.getInstance().getImagePath(), "thvideo" + System.currentTimeMillis());Bitmap bitmap = null;FileOutputStream fos = null;try {if (!file.getParentFile().exists()) {file.getParentFile().mkdirs();}bitmap = ThumbnailUtils.createVideoThumbnail(videoPath, 3);if (bitmap == null) {EMLog.d("chatactivity", "problem load video thumbnail bitmap,use default icon");bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.app_panel_video_icon);}fos = new FileOutputStream(file);bitmap.compress(CompressFormat.JPEG, 100, fos);} catch (Exception e) {e.printStackTrace();} finally {if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}fos = null;}if (bitmap != null) {bitmap.recycle();bitmap = null;}}sendVideo(videoPath, file.getAbsolutePath(), duration / 1000);} else if (requestCode == REQUEST_CODE_LOCAL) { // 发送本地图片if (data != null) {Uri selectedImage = data.getData();if (selectedImage != null) {sendPicByUri(selectedImage);}}} else if (requestCode == REQUEST_CODE_SELECT_FILE) { // 发送选择的文件if (data != null) {Uri uri = data.getData();if (uri != null) {sendFile(uri);}}} else if (requestCode == REQUEST_CODE_MAP) { // 地图double latitude = data.getDoubleExtra("latitude", 0);double longitude = data.getDoubleExtra("longitude", 0);String locationAddress = data.getStringExtra("address");if (locationAddress != null && !locationAddress.equals("")) {more(more);sendLocationMsg(latitude, longitude, "", locationAddress);} else {String st = getResources().getString(R.string.unable_to_get_loaction);Toast.makeText(this, st, 0).show();}// 重发消息} else if (requestCode == REQUEST_CODE_TEXT || requestCode == REQUEST_CODE_VOICE || requestCode == REQUEST_CODE_PICTURE || requestCode == REQUEST_CODE_LOCATION || requestCode == REQUEST_CODE_VIDEO || requestCode == REQUEST_CODE_FILE) {resendMessage();} else if (requestCode == REQUEST_CODE_COPY_AND_PASTE) {// 粘贴if (!TextUtils.isEmpty(clipboard.getText())) {String pasteText = clipboard.getText().toString();if (pasteText.startsWith(COPY_IMAGE)) {// 把图片前缀去掉,还原成正常的pathsendPicture(pasteText.replace(COPY_IMAGE, ""));}}} else if (requestCode == REQUEST_CODE_ADD_TO_BLACKLIST) { // 移入黑名单EMMessage deleteMsg = (EMMessage) adapter.getItem(data.getIntExtra("position", -1));addUserToBlacklist(deleteMsg.getFrom());} else if (conversation.getMsgCount() > 0) {adapter.refresh();setResult(RESULT_OK);} else if (requestCode == REQUEST_CODE_GROUP_DETAIL) {adapter.refresh();}}}

首先是退出组的消息,收到消息后直接退出该聊天界面。

接下来是上下文菜单的消息,其中转发消息将进入ForwardMessageActivity界面,选择联系人进行转发消息。




这篇关于环信UI开源Demo情景分析九、聊天界面(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/305705

相关文章

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

element-ui下拉输入框+resetFields无法回显的问题解决

《element-ui下拉输入框+resetFields无法回显的问题解决》本文主要介绍了在使用ElementUI的下拉输入框时,点击重置按钮后输入框无法回显数据的问题,具有一定的参考价值,感兴趣的... 目录描述原因问题重现解决方案方法一方法二总结描述第一次进入页面,不做任何操作,点击重置按钮,再进行下

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

python-nmap实现python利用nmap进行扫描分析

《python-nmap实现python利用nmap进行扫描分析》Nmap是一个非常用的网络/端口扫描工具,如果想将nmap集成进你的工具里,可以使用python-nmap这个python库,它提供了... 目录前言python-nmap的基本使用PortScanner扫描PortScannerAsync异

Oracle数据库执行计划的查看与分析技巧

《Oracle数据库执行计划的查看与分析技巧》在Oracle数据库中,执行计划能够帮助我们深入了解SQL语句在数据库内部的执行细节,进而优化查询性能、提升系统效率,执行计划是Oracle数据库优化器为... 目录一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 S

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置