从会话列表点击一个会话进入聊天界面,现在咱们先来看看聊天界面的清单配置。(在代码中注释比较多,有兴趣的童雪不要只关注文本)
<!-- 聊天页面 -->
<activity
android:name=".activity.ChatActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/horizontal_slide"
android:windowSoftInputMode="adjustResize" >
</activity>
adjustResize:该Activity总是调整屏幕的大小以便留出软键盘的空间。
界面布局相对复杂一点,其中在ListVIew上面还有一个语音提示的布局。
<RelativeLayout
android: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" >
<ImageView
android:id="@+id/mic_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/record_animate_01" />
<TextView
android: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);
//显示表情Viewpager
expressionViewpager = (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), };
// 表情list
reslist = getExpressionRes(35);
// 初始化表情viewpager
List<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() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public 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 name
if (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);
// 把此会话的未读数置为0
conversation.resetUnreadMsgCount();
//加载消息
// 初始化db时,每个conversation加载数目是getChatOptions().getNumberOfMessagesLoaded
// 这个数目如果比用户期望进入会话界面时显示的个数不一样,就多加载一些
final List<EMMessage> msgs = conversation.getAllMessages();
int msgCount = msgs != null ? msgs.size() : 0;
//pagesize = 20
if (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() {
@Override
public 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 null
String forward_msg_id = getIntent().getStringExtra("forward_msg_id");
if (forward_msg_id != null) {
// 显示发送要转发的消息
forwardMessage(forward_msg_id);
}
}
该方法主要是对聊天内容区的一些设置,通过对聊天类型的判断从而设置Name,在聊天区域被触摸的时候更改输入栏。监听群T事件以及获得转发内容。
接下来看一下点击事件。
/**
* 消息图标点击事件
*
* @param view
*/
@Override
public 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();
else
startActivity(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();
else
startActivity(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);
// 设置消息body
message.addBody(txtBody);
// 设置要发给谁,用户username或者群聊groupid
message.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";
@Override
protected 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();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
}
<!-- choose video -->
<activity
android: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() {
}
@Override
public 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 asynchronously
mImageResizer = 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._ID
int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID));
// 名称:MediaStore.Audio.Media.TITLE
String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE));
// 路径:MediaStore.Audio.Media.DATA
String url = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
// 总播放时长:MediaStore.Audio.Media.DURATION
int duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));
// 大小:MediaStore.Audio.Media.SIZE
int 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);
}
@Override
public int getCount() {
return mList.size() + 1;
}
@Override
public Object getItem(int position) {
return (position == 0) ? null : mList.get(position - 1);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public 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 width
if (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 runs
String 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中设置布局。
@Override
public 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() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
// Pause fetcher to ensure smoother scrolling when flinging
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
// Before Honeycomb pause image loading on scroll to help
// with performance
if (!Utils.hasHoneycomb()) {
mImageResizer.setPauseWork(true);
}
} else {
mImageResizer.setPauseWork(false);
}
}
@Override
public 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)
@Override
public 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布局完成时自动调用。
点击事件:
@Override
public 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);
// 限制大小不能超过10M
if (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用来录像,此功能在这里不详细分析,有兴趣的同学可以自己去学习以下。
@Override
public 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.DATA
filePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
// 总播放时长:MediaStore.Audio.Media.DURATION
duration = 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();
}
}
}
录像之后返回此界面,获取地址和时长后返回聊天界面。
@Override
public void onResume() {
super.onResume();
mImageResizer.setExitTasksEarly(false);
mAdapter.notifyDataSetChanged();
}
@Override
public 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)) {
// 把图片前缀去掉,还原成正常的path
sendPicture(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界面,选择联系人进行转发消息。