This commit is contained in:
zlzw 2023-10-11 00:22:16 +08:00
parent da6ca4fbbb
commit 6d495d11fd
16 changed files with 1744 additions and 135 deletions

View File

@ -6,6 +6,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -15,25 +16,32 @@ import androidx.fragment.app.Fragment;
*/ */
public abstract class BaseFragment extends Fragment { public abstract class BaseFragment extends Fragment {
public Context mContext; public Context mContext;
private View itemView;
@Nullable @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View itemView = createView(inflater, container, savedInstanceState); View itemView = createView(inflater, container, savedInstanceState);
this.mContext = getContext(); this.mContext = getContext();
this.itemView = itemView;
initView(itemView); initView(itemView);
return itemView; return itemView;
} }
public <T extends View> T findViewById(@IdRes int id) {
return itemView.findViewById(id);
}
/** /**
* 初始化页面 * 初始化页面
*
* @param itemView 布局view * @param itemView 布局view
*/ */
public abstract void initView(View itemView); public abstract void initView(View itemView);
/** /**
* 创建页面 * 创建页面
*
* @return 布局 * @return 布局
*/ */
public abstract View createView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); public abstract View createView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);

View File

@ -0,0 +1,41 @@
package com.shayu.onetoone.activity.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.shayu.onetoone.R;
import com.shayu.onetoone.adapter.GiftListAdapter;
import com.shayu.onetoone.bean.GiftBean;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
public class GiftDialogFragment extends BaseFragment {
private RecyclerView recyclerView;
private GiftListAdapter mAdapter;
List<GiftBean> list;
public GiftDialogFragment() {
}
public void setList(List<GiftBean> list) {
this.list = list;
mAdapter.setList(list);
}
@Override
public void initView(View itemView) {
recyclerView = findViewById(R.id.recyclerView);
mAdapter = new GiftListAdapter(getContext());
recyclerView.setAdapter(mAdapter);
}
@Override
public View createView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_gift, container, false);
}
}

View File

@ -30,6 +30,8 @@ import com.shayu.onetoone.listener.OnDialogClickListener;
import com.shayu.onetoone.listener.OnSendMessageListener; import com.shayu.onetoone.listener.OnSendMessageListener;
import com.shayu.onetoone.manager.OTONetManager; import com.shayu.onetoone.manager.OTONetManager;
import com.shayu.onetoone.manager.SendMessageManager; import com.shayu.onetoone.manager.SendMessageManager;
import com.shayu.onetoone.view.MsgInputPanelForAudio;
import com.shayu.onetoone.view.MsgInputPanelForGift;
import com.yunbao.common.CommonAppConfig; import com.yunbao.common.CommonAppConfig;
import com.yunbao.common.glide.ImgLoader; import com.yunbao.common.glide.ImgLoader;
import com.yunbao.common.http.base.HttpCallback; import com.yunbao.common.http.base.HttpCallback;
@ -67,12 +69,6 @@ public class ChatMessageFragment extends AbsConversationFragment {
ProcessImageUtil cameraUtil; ProcessImageUtil cameraUtil;
String targetId; String targetId;
Conversation.ConversationType conversationType = Conversation.ConversationType.PRIVATE; Conversation.ConversationType conversationType = Conversation.ConversationType.PRIVATE;
ViewGroup audioLayout;
ImageView btnAudio;
ImageView btnText;
ImageView btnClose;
private float mLastTouchY;
private boolean mUpDirection;
RoundedImageView avatar; RoundedImageView avatar;
@ -88,9 +84,9 @@ public class ChatMessageFragment extends AbsConversationFragment {
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
targetId = getActivity().getIntent().getStringExtra("targetId");
initBtn(); initBtn();
initChat(); initChat();
targetId = getActivity().getIntent().getStringExtra("targetId");
cameraUtil = new ProcessImageUtil(getActivity(), "com.shayu.onetoone.fileprovider"); cameraUtil = new ProcessImageUtil(getActivity(), "com.shayu.onetoone.fileprovider");
mRongExtension.setVisibility(View.VISIBLE); mRongExtension.setVisibility(View.VISIBLE);
@ -176,114 +172,32 @@ public class ChatMessageFragment extends AbsConversationFragment {
}); });
} }
@SuppressLint("ClickableViewAccessibility") MsgInputPanelForAudio audio;
MsgInputPanelForGift giftPanel;
private void initBtn() { private void initBtn() {
try { try {
Field field = mRongExtension.getInputPanel().getClass().getDeclaredField("mInputPanel"); Field field = mRongExtension.getInputPanel().getClass().getDeclaredField("mInputPanel");
field.setAccessible(true); field.setAccessible(true);
mInputPanel = (View) field.get(mRongExtension.getInputPanel()); mInputPanel = (View) field.get(mRongExtension.getInputPanel());
assert mInputPanel != null; assert mInputPanel != null;
mSendBtn = mInputPanel.findViewById(R.id.send_btn); mSendBtn = mInputPanel.getRootView().findViewById(R.id.send_btn);
ImageView mVoiceToggleBtn = (ImageView) mInputPanel.findViewById(R.id.input_panel_voice_toggle);
img = mInputPanel.getRootView().findViewById(R.id.input_panel_image_btn);
video = mInputPanel.getRootView().findViewById(R.id.input_panel_video_btn);
call = mInputPanel.getRootView().findViewById(R.id.input_panel_call_btn);
gift = mInputPanel.getRootView().findViewById(R.id.input_panel_gift_btn);
mEditText = mInputPanel.getRootView().findViewById(R.id.edit_btn);
ImageView mVoiceToggleBtn = (ImageView) mInputPanel.getRootView().findViewById(R.id.input_panel_voice_toggle);
assert mVoiceToggleBtn != null; assert mVoiceToggleBtn != null;
audio = new MsgInputPanelForAudio(targetId, getActivity(), mRongExtension);
mVoiceToggleBtn.setOnClickListener(v -> { giftPanel = new MsgInputPanelForGift(targetId, getActivity(), mRongExtension);
SendMessageManager.sendMessageForAudio(targetId, new OnSendMessageListener() { mVoiceToggleBtn.setOnClickListener(view -> {
@Override audio.show();
public void onSuccess(String token) {
super.onSuccess(token);
ChatMessageFragment.this.token = token;
changeAudioLayout();
}
@Override
public void onError(int status, String msg) {
super.onError(status, msg);
if (status == OnSendMessageListener.STATUS_NOT_PRICE) {
new TipsDialog(mContext)
.setTitle("餘額不足")
.setContent(String.format("聊天每條續消耗%s鑽您可通過充值獲取更多鑽石以便繼續聊天", msg))
.setOnDialogClickListener(new OnDialogClickListener() {
@Override
public void onApply(Dialog dialog) {
super.onApply(dialog);
}
}).showDialog();
} else {
ToastUtil.show(msg);
}
}
});
}); });
img = mInputPanel.findViewById(R.id.input_panel_image_btn);
video = mInputPanel.findViewById(R.id.input_panel_video_btn);
call = mInputPanel.findViewById(R.id.input_panel_call_btn);
gift = mInputPanel.findViewById(R.id.input_panel_gift_btn);
audioLayout = mInputPanel.findViewById(R.id.audio_layout);
audioLayout.addView(LayoutInflater.from(mContext).inflate(R.layout.view_message_input_audio, (ViewGroup) mInputPanel,false));
btnAudio = audioLayout.findViewById(R.id.audio_btn);
btnText = audioLayout.findViewById(R.id.text_btn);
btnClose = audioLayout.findViewById(R.id.close_btn);
mEditText = audioLayout.findViewById(R.id.edit_btn);
btnText.setOnClickListener(v -> changeAudioLayout());
btnClose.setOnClickListener(v -> changeAudioLayout());
btnAudio.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
float mOffsetLimit = 70.0F * v.getContext().getResources().getDisplayMetrics().density;
String[] permissions = new String[]{"android.permission.RECORD_AUDIO"};
if (!PermissionCheckUtil.checkPermissions(v.getContext(), permissions) && event.getAction() == 0) {
PermissionCheckUtil.requestPermissions(ChatMessageFragment.this, permissions, 100);
return true;
} else {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (AudioPlayManager.getInstance().isPlaying()) {
AudioPlayManager.getInstance().stopPlay();
}
if (RongOperationPermissionUtils.isOnRequestHardwareResource()) {
Toast.makeText(v.getContext(), v.getContext().getResources().getString(io.rong.imkit.R.string.rc_voip_occupying), Toast.LENGTH_SHORT).show();
return true;
}
ChatMessageFragment.this.mLastTouchY = event.getY();
ChatMessageFragment.this.mUpDirection = false;
AudioRecordManager.getInstance().startRecord(v.getRootView(), mRongExtension.getConversationIdentifier());
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (ChatMessageFragment.this.mLastTouchY - event.getY() > mOffsetLimit && !ChatMessageFragment.this.mUpDirection) {
AudioRecordManager.getInstance().willCancelRecord();
ChatMessageFragment.this.mUpDirection = true;
} else if (event.getY() - ChatMessageFragment.this.mLastTouchY > -mOffsetLimit && ChatMessageFragment.this.mUpDirection) {
AudioRecordManager.getInstance().continueRecord();
ChatMessageFragment.this.mUpDirection = false;
}
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
AudioRecordManager.getInstance().stopRecord();
SendMessageManager.onCallSuccess(token, new OnSendMessageListener() {
@Override
public void onError(int status, String msg) {
super.onError(status, msg);
ToastUtil.show(msg);
}
});
}
if (mRongExtension.getConversationIdentifier().getType().equals(Conversation.ConversationType.PRIVATE)) {
RongIMClient.getInstance().sendTypingStatus(mRongExtension.getConversationIdentifier().getType(),
mRongExtension.getConversationIdentifier().getTargetId(), "RC:VcMsg");
}
}
return true;
}
});
gift.setOnClickListener(v -> { gift.setOnClickListener(v -> {
new GiftDialog(mContext).showDialog(); giftPanel.show();
}); });
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@ -291,14 +205,6 @@ public class ChatMessageFragment extends AbsConversationFragment {
} }
} }
private void changeAudioLayout() {
if (audioLayout.getVisibility() == View.VISIBLE) {
audioLayout.setVisibility(View.GONE);
SendMessageManager.cancel(token);
} else {
audioLayout.setVisibility(View.VISIBLE);
}
}
private void sendText() { private void sendText() {
Conversation.ConversationType conversationType = Conversation.ConversationType.PRIVATE; Conversation.ConversationType conversationType = Conversation.ConversationType.PRIVATE;

View File

@ -14,14 +14,17 @@ import com.shayu.onetoone.R;
import com.shayu.onetoone.bean.GiftBean; import com.shayu.onetoone.bean.GiftBean;
import com.yunbao.common.glide.ImgLoader; import com.yunbao.common.glide.ImgLoader;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class GiftListAdapter extends RecyclerView.Adapter<GiftListAdapter.ViewHolder> { public class GiftListAdapter extends RecyclerView.Adapter<GiftListAdapter.ViewHolder> {
private Context mContext; private Context mContext;
private List<GiftBean> list; private List<GiftBean> list;
private int selectPosition = 0;
public GiftListAdapter(Context mContext) { public GiftListAdapter(Context mContext) {
this.mContext = mContext; this.mContext = mContext;
list = new ArrayList<>();
} }
public void setList(List<GiftBean> list) { public void setList(List<GiftBean> list) {
@ -45,7 +48,7 @@ public class GiftListAdapter extends RecyclerView.Adapter<GiftListAdapter.ViewHo
return list.size(); return list.size();
} }
public static class ViewHolder extends RecyclerView.ViewHolder { public class ViewHolder extends RecyclerView.ViewHolder {
private ImageView giftIcon; private ImageView giftIcon;
private TextView giftName; private TextView giftName;
private TextView money; private TextView money;
@ -56,9 +59,10 @@ public class GiftListAdapter extends RecyclerView.Adapter<GiftListAdapter.ViewHo
giftIcon = itemView.findViewById(R.id.gift_icon); giftIcon = itemView.findViewById(R.id.gift_icon);
giftName = itemView.findViewById(R.id.gift_name); giftName = itemView.findViewById(R.id.gift_name);
money = itemView.findViewById(R.id.money); money = itemView.findViewById(R.id.money);
select.findViewById(R.id.bg_select); select = itemView.findViewById(R.id.bg_select);
itemView.setOnClickListener(v -> { itemView.setOnClickListener(v -> {
select.setVisibility(View.VISIBLE); selectPosition = getAbsoluteAdapterPosition();
notifyDataSetChanged();
}); });
} }
@ -67,6 +71,12 @@ public class GiftListAdapter extends RecyclerView.Adapter<GiftListAdapter.ViewHo
money.setText(giftBean.getNeedcoin() + ""); money.setText(giftBean.getNeedcoin() + "");
ImgLoader.display(itemView.getContext(), giftBean.getGifticon(), giftIcon); ImgLoader.display(itemView.getContext(), giftBean.getGifticon(), giftIcon);
select.setVisibility(View.GONE); select.setVisibility(View.GONE);
if (position == selectPosition) {
select.setVisibility(View.VISIBLE);
} else {
select.setVisibility(View.GONE);
}
} }
} }
} }

View File

@ -12,9 +12,13 @@ import com.shayu.onetoone.R;
import com.shayu.onetoone.adapter.GiftListAdapter; import com.shayu.onetoone.adapter.GiftListAdapter;
import com.shayu.onetoone.bean.GiftBean; import com.shayu.onetoone.bean.GiftBean;
import com.shayu.onetoone.manager.OTONetManager; import com.shayu.onetoone.manager.OTONetManager;
import com.shayu.onetoone.widget.PagerConfig;
import com.shayu.onetoone.widget.PagerGridLayoutManager;
import com.shayu.onetoone.widget.PagerGridSnapHelper;
import com.yunbao.common.dialog.AbsDialogPopupWindow; import com.yunbao.common.dialog.AbsDialogPopupWindow;
import com.yunbao.common.http.base.HttpCallback; import com.yunbao.common.http.base.HttpCallback;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class GiftDialog extends AbsDialogPopupWindow { public class GiftDialog extends AbsDialogPopupWindow {
@ -46,8 +50,13 @@ public class GiftDialog extends AbsDialogPopupWindow {
money = findViewById(R.id.money); money = findViewById(R.id.money);
topUpBtn = findViewById(R.id.top_up_btn); topUpBtn = findViewById(R.id.top_up_btn);
sendBtn = findViewById(R.id.send_btn); sendBtn = findViewById(R.id.send_btn);
mAdapter = new GiftListAdapter(mContext); mAdapter = new GiftListAdapter(mContext);
PagerGridLayoutManager manager = new PagerGridLayoutManager(2, 4, PagerGridLayoutManager.HORIZONTAL);
gifList.setLayoutManager(manager);
PagerGridSnapHelper pageSnapHelper = new PagerGridSnapHelper();
pageSnapHelper.attachToRecyclerView(gifList);
manager.setAllowContinuousScroll(false);
PagerConfig.setMillisecondsPreInch(150);
gifList.setAdapter(mAdapter); gifList.setAdapter(mAdapter);
initData(); initData();
} }
@ -57,7 +66,11 @@ public class GiftDialog extends AbsDialogPopupWindow {
.getGiftList(new HttpCallback<List<GiftBean>>() { .getGiftList(new HttpCallback<List<GiftBean>>() {
@Override @Override
public void onSuccess(List<GiftBean> data) { public void onSuccess(List<GiftBean> data) {
mAdapter.setList(data); List<GiftBean> list=new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.addAll(data);
}
mAdapter.setList(list);
} }
@Override @Override

View File

@ -0,0 +1,53 @@
package com.shayu.onetoone.view;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.shayu.onetoone.R;
import com.yunbao.common.utils.ToastUtil;
import java.lang.reflect.Field;
import androidx.fragment.app.FragmentActivity;
import io.rong.imkit.conversation.extension.RongExtension;
public abstract class AbsInputPanel {
FragmentActivity mContext;
RongExtension mRongExtension;
View mInputPanel;
String targetId;
ViewGroup rootLayout;
View rootView;
public AbsInputPanel(String targetId, FragmentActivity mContext, RongExtension mRongExtension, int layout) {
this.mContext = mContext;
this.mRongExtension = mRongExtension;
this.targetId = targetId;
try {
Field field = mRongExtension.getInputPanel().getClass().getDeclaredField("mInputPanel");
field.setAccessible(true);
mInputPanel = (View) field.get(mRongExtension.getInputPanel());
rootLayout = mInputPanel.findViewById(R.id.audio_layout);
assert rootLayout != null;
rootView = LayoutInflater.from(mContext).inflate(layout, mRongExtension, false);
init(rootView);
} catch (Exception e) {
e.printStackTrace();
}
}
public int show() {
if (rootLayout.getVisibility() == View.VISIBLE) {
rootLayout.setVisibility(View.GONE);
return View.GONE;
}
rootLayout.removeAllViews();
rootLayout.addView(rootView);
rootLayout.setVisibility(View.VISIBLE);
return View.VISIBLE;
}
public abstract void init(View viewGroup);
}

View File

@ -0,0 +1,192 @@
package com.shayu.onetoone.view;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import com.blankj.utilcode.util.PermissionUtils;
import com.shayu.onetoone.R;
import com.shayu.onetoone.dialog.TipsDialog;
import com.shayu.onetoone.listener.OnDialogClickListener;
import com.shayu.onetoone.listener.OnSendMessageListener;
import com.shayu.onetoone.manager.SendMessageManager;
import com.yunbao.common.utils.ToastUtil;
import java.lang.reflect.Field;
import androidx.fragment.app.FragmentActivity;
import io.rong.imkit.conversation.extension.RongExtension;
import io.rong.imkit.manager.AudioPlayManager;
import io.rong.imkit.manager.AudioRecordManager;
import io.rong.imkit.utils.PermissionCheckUtil;
import io.rong.imkit.utils.RongOperationPermissionUtils;
import io.rong.imlib.RongIMClient;
import io.rong.imlib.model.Conversation;
public class MsgInputPanelForAudio extends AbsInputPanel {
ImageView btnAudio;
ImageView btnText;
ImageView btnClose;
private float mLastTouchY;
private boolean mUpDirection;
private String token;
OnSendMessageListener listener;
public MsgInputPanelForAudio(String targetId, FragmentActivity mContext, RongExtension mRongExtension) {
super(targetId, mContext, mRongExtension, R.layout.view_message_input_audio);
}
public void setOnSendMessageListener(OnSendMessageListener listener) {
this.listener = listener;
}
@Override
public int show() {
if (super.show() == View.GONE) {
SendMessageManager.cancel(token);
return View.GONE;
}
return View.VISIBLE;
}
private boolean isAudio;
@SuppressLint("ClickableViewAccessibility")
@Override
public void init(View viewGroup) {
btnAudio = viewGroup.findViewById(R.id.audio_btn);
btnText = viewGroup.findViewById(R.id.text_btn);
btnClose = viewGroup.findViewById(R.id.close_btn);
btnAudio.setOnTouchListener((v, event) -> {
System.out.println("点击事件:" + event.getAction()+" isAudio = "+isAudio);
float mOffsetLimit = 70.0F * v.getContext().getResources().getDisplayMetrics().density;
String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO};
if (!PermissionCheckUtil.checkPermissions(v.getContext(), permissions) && event.getAction() == 0) {
PermissionCheckUtil.requestPermissions(mContext, permissions, 100);
PermissionUtils.permission(Manifest.permission.RECORD_AUDIO).request();
System.out.println("点击事件 没权限掉了");
return true;
} else {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
System.out.println("点击事件:点下 isAudio = "+isAudio);
if (isAudio) {
if (onDown(v, event.getY())) {
System.out.println("点击事件:内部点击,返回掉了");
return true;
}
} else {
System.out.println("点击事件准备进token");
toToken(v, event.getY());
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (isAudio) {
onMove(v, event, mOffsetLimit);
}
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
if (isAudio) {
onUp(v, event);
}
}
if (isAudio) {
if (mRongExtension.getConversationIdentifier().getType().equals(Conversation.ConversationType.PRIVATE)) {
RongIMClient.getInstance().sendTypingStatus(mRongExtension.getConversationIdentifier().getType(),
mRongExtension.getConversationIdentifier().getTargetId(), "RC:VcMsg");
}
}
}
return true;
});
}
private void toToken(View v, float mLastTouchY) {
System.out.println("点击事件调用token");
SendMessageManager.sendMessageForAudio(targetId, new OnSendMessageListener() {
@Override
public void onSuccess(String token) {
super.onSuccess(token);
MsgInputPanelForAudio.this.token = token;
isAudio = true;
onDown(v, mLastTouchY);
}
@Override
public void onError(int status, String msg) {
super.onError(status, msg);
if (status == OnSendMessageListener.STATUS_NOT_PRICE) {
new TipsDialog(mContext)
.setTitle("餘額不足")
.setContent(String.format("聊天每條續消耗%s鑽您可通過充值獲取更多鑽石以便繼續聊天", msg))
.setOnDialogClickListener(new OnDialogClickListener() {
@Override
public void onApply(Dialog dialog) {
super.onApply(dialog);
}
}).showDialog();
} else {
ToastUtil.show(msg);
}
}
});
}
private boolean onDown(View v, float mLastTouchY) {
System.out.println("点击事件 mLastTouchY = "+mLastTouchY);
if (AudioPlayManager.getInstance().isPlaying()) {
AudioPlayManager.getInstance().stopPlay();
}
if (RongOperationPermissionUtils.isOnRequestHardwareResource()) {
Toast.makeText(v.getContext(), v.getContext().getResources().getString(io.rong.imkit.R.string.rc_voip_occupying), Toast.LENGTH_SHORT).show();
return true;
}
MsgInputPanelForAudio.this.mLastTouchY = mLastTouchY;
MsgInputPanelForAudio.this.mUpDirection = false;
AudioRecordManager.getInstance().startRecord(v.getRootView(), mRongExtension.getConversationIdentifier());
return false;
}
private boolean onMove(View v, MotionEvent event, float mOffsetLimit) {
if (MsgInputPanelForAudio.this.mLastTouchY - event.getY() > mOffsetLimit && !MsgInputPanelForAudio.this.mUpDirection) {
AudioRecordManager.getInstance().willCancelRecord();
MsgInputPanelForAudio.this.mUpDirection = true;
} else if (event.getY() - MsgInputPanelForAudio.this.mLastTouchY > -mOffsetLimit && MsgInputPanelForAudio.this.mUpDirection) {
AudioRecordManager.getInstance().continueRecord();
MsgInputPanelForAudio.this.mUpDirection = false;
}
return false;
}
private boolean onUp(View v, MotionEvent event) {
AudioRecordManager.getInstance().stopRecord();
if (mUpDirection) {
ToastUtil.show("取消发送");
SendMessageManager.cancel(token);
isAudio = false;
return false;
}
SendMessageManager.onCallSuccess(token, new OnSendMessageListener() {
@Override
public void onError(int status, String msg) {
super.onError(status, msg);
ToastUtil.show(msg);
isAudio = false;
}
});
return false;
}
}

View File

@ -0,0 +1,75 @@
package com.shayu.onetoone.view;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.shayu.onetoone.R;
import com.shayu.onetoone.adapter.GiftListAdapter;
import com.shayu.onetoone.bean.GiftBean;
import com.shayu.onetoone.manager.OTONetManager;
import com.shayu.onetoone.widget.PagerConfig;
import com.shayu.onetoone.widget.PagerGridLayoutManager;
import com.shayu.onetoone.widget.PagerGridSnapHelper;
import com.yunbao.common.http.base.HttpCallback;
import com.yunbao.common.utils.ToastUtil;
import java.util.ArrayList;
import java.util.List;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import io.rong.imkit.conversation.extension.RongExtension;
public class MsgInputPanelForGift extends AbsInputPanel{
RecyclerView gifList;
GiftListAdapter mAdapter;
TextView money;
Button topUpBtn;
Button sendBtn;
public MsgInputPanelForGift(String targetId, FragmentActivity fragmentActivity, RongExtension mRongExtension) {
super(targetId, fragmentActivity, mRongExtension, R.layout.view_message_input_gift);
}
@Override
public int show() {
return super.show();
}
@Override
public void init(View viewGroup) {
gifList = viewGroup.findViewById(R.id.gift_list);
money = viewGroup.findViewById(R.id.money);
topUpBtn = viewGroup.findViewById(R.id.top_up_btn);
sendBtn =viewGroup.findViewById(R.id.send_btn);
mAdapter = new GiftListAdapter(mContext);
PagerGridLayoutManager manager = new PagerGridLayoutManager(2, 4, PagerGridLayoutManager.HORIZONTAL);
gifList.setLayoutManager(manager);
PagerGridSnapHelper pageSnapHelper = new PagerGridSnapHelper();
pageSnapHelper.attachToRecyclerView(gifList);
manager.setAllowContinuousScroll(false);
PagerConfig.setMillisecondsPreInch(150);
gifList.setAdapter(mAdapter);
initData();
}
private void initData() {
OTONetManager.getInstance(mContext)
.getGiftList(new HttpCallback<List<GiftBean>>() {
@Override
public void onSuccess(List<GiftBean> data) {
List<GiftBean> list=new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.addAll(data);
}
mAdapter.setList(list);
}
@Override
public void onError(String error) {
}
});
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-09-20 16:32:43
*
* GitHub: https://github.com/GcsSloop
* WeiBo: http://weibo.com/GcsSloop
* WebSite: http://www.gcssloop.com
*/
package com.shayu.onetoone.widget;
import android.util.Log;
/**
* 作用Pager配置
* 作者GcsSloop
* 摘要主要用于Log的显示与关闭
*/
public class PagerConfig {
private static final String TAG = "PagerGrid";
private static boolean sShowLog = false;
private static int sFlingThreshold = 1000; // Fling 阀值滚动速度超过该阀值才会触发滚动
private static float sMillisecondsPreInch = 60f; // 每一个英寸滚动需要的微秒数数值越大速度越慢
/**
* 判断是否输出日志
*
* @return true 输出false 不输出
*/
public static boolean isShowLog() {
return sShowLog;
}
/**
* 设置是否输出日志
*
* @param showLog 是否输出
*/
public static void setShowLog(boolean showLog) {
sShowLog = showLog;
}
/**
* 获取当前滚动速度阀值
*
* @return 当前滚动速度阀值
*/
public static int getFlingThreshold() {
return sFlingThreshold;
}
/**
* 设置当前滚动速度阀值
*
* @param flingThreshold 滚动速度阀值
*/
public static void setFlingThreshold(int flingThreshold) {
sFlingThreshold = flingThreshold;
}
/**
* 获取滚动速度 英寸/微秒
*
* @return 英寸滚动速度
*/
public static float getMillisecondsPreInch() {
return sMillisecondsPreInch;
}
/**
* 设置像素滚动速度 英寸/微秒
*
* @param millisecondsPreInch 英寸滚动速度
*/
public static void setMillisecondsPreInch(float millisecondsPreInch) {
sMillisecondsPreInch = millisecondsPreInch;
}
//--- 日志 -------------------------------------------------------------------------------------
public static void Logi(String msg) {
if (!PagerConfig.isShowLog()) return;
Log.i(TAG, msg);
}
public static void Loge(String msg) {
if (!PagerConfig.isShowLog()) return;
Log.e(TAG, msg);
}
}

View File

@ -0,0 +1,908 @@
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-09-20 16:32:43
*
* GitHub: https://github.com/GcsSloop
* WeiBo: http://weibo.com/GcsSloop
* WebSite: http://www.gcssloop.com
*/
package com.shayu.onetoone.widget;
import android.annotation.SuppressLint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import static android.view.View.MeasureSpec.EXACTLY;
import static com.shayu.onetoone.widget.PagerConfig.Loge;
import static com.shayu.onetoone.widget.PagerConfig.Logi;
import static com.yunbao.common.views.weight.VerticalViewPager.SCROLL_STATE_IDLE;
/**
* 作用分页的网格布局管理器
* 作者GcsSloop
* 摘要
* 1. 网格布局
* 2. 支持水平分页和垂直分页
* 3. 杜绝高内存占用
*/
public class PagerGridLayoutManager extends RecyclerView.LayoutManager
implements RecyclerView.SmoothScroller.ScrollVectorProvider {
private static final String TAG = PagerGridLayoutManager.class.getSimpleName();
public static final int VERTICAL = 0; // 垂直滚动
public static final int HORIZONTAL = 1; // 水平滚动
@IntDef({VERTICAL, HORIZONTAL})
public @interface OrientationType {} // 滚动类型
@OrientationType
private int mOrientation; // 默认水平滚动
private int mOffsetX = 0; // 水平滚动距离(偏移量)
private int mOffsetY = 0; // 垂直滚动距离(偏移量)
private int mRows; // 行数
private int mColumns; // 列数
private int mOnePageSize; // 一页的条目数量
private SparseArray<Rect> mItemFrames; // 条目的显示区域
private int mItemWidth = 0; // 条目宽度
private int mItemHeight = 0; // 条目高度
private int mWidthUsed = 0; // 已经使用空间用于测量View
private int mHeightUsed = 0; // 已经使用空间用于测量View
private int mMaxScrollX; // 最大允许滑动的宽度
private int mMaxScrollY; // 最大允许滑动的高度
private int mScrollState = SCROLL_STATE_IDLE; // 滚动状态
private boolean mAllowContinuousScroll = true; // 是否允许连续滚动
private RecyclerView mRecyclerView;
/**
* 构造函数
*
* @param rows 行数
* @param columns 列数
* @param orientation 方向
*/
public PagerGridLayoutManager(@IntRange(from = 1, to = 100) int rows,
@IntRange(from = 1, to = 100) int columns,
@OrientationType int orientation) {
mItemFrames = new SparseArray<>();
mOrientation = orientation;
mRows = rows;
mColumns = columns;
mOnePageSize = mRows * mColumns;
}
@Override
public void onAttachedToWindow(RecyclerView view) {
super.onAttachedToWindow(view);
mRecyclerView = view;
}
//--- 处理布局 ----------------------------------------------------------------------------------
/**
* 布局子View
*
* @param recycler Recycler
* @param state State
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
Logi("Item onLayoutChildren");
Logi("Item onLayoutChildren isPreLayout = " + state.isPreLayout());
Logi("Item onLayoutChildren isMeasuring = " + state.isMeasuring());
Loge("Item onLayoutChildren state = " + state);
// 如果是 preLayout 则不重新布局
if (state.isPreLayout() || !state.didStructureChange()) {
return;
}
if (getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
// 页面变化回调
setPageCount(0);
setPageIndex(0, false);
return;
} else {
setPageCount(getTotalPageCount());
setPageIndex(getPageIndexByOffset(), false);
}
// 计算页面数量
int mPageCount = getItemCount() / mOnePageSize;
if (getItemCount() % mOnePageSize != 0) {
mPageCount++;
}
// 计算可以滚动的最大数值并对滚动距离进行修正
if (canScrollHorizontally()) {
mMaxScrollX = (mPageCount - 1) * getUsableWidth();
mMaxScrollY = 0;
if (mOffsetX > mMaxScrollX) {
mOffsetX = mMaxScrollX;
}
} else {
mMaxScrollX = 0;
mMaxScrollY = (mPageCount - 1) * getUsableHeight();
if (mOffsetY > mMaxScrollY) {
mOffsetY = mMaxScrollY;
}
}
// 接口回调
// setPageCount(mPageCount);
// setPageIndex(mCurrentPageIndex, false);
Logi("count = " + getItemCount());
if (mItemWidth <= 0) {
mItemWidth = getUsableWidth() / mColumns;
}
if (mItemHeight <= 0) {
mItemHeight = getUsableHeight() / mRows;
}
mWidthUsed = getUsableWidth() - mItemWidth;
mHeightUsed = getUsableHeight() - mItemHeight;
// 预存储两页的View显示区域
for (int i = 0; i < mOnePageSize * 2; i++) {
getItemFrameByPosition(i);
}
if (mOffsetX == 0 && mOffsetY == 0) {
// 预存储View
for (int i = 0; i < mOnePageSize; i++) {
if (i >= getItemCount()) break; // 防止数据过少时导致数组越界异常
View view = recycler.getViewForPosition(i);
addView(view);
measureChildWithMargins(view, mWidthUsed, mHeightUsed);
}
}
// 回收和填充布局
recycleAndFillItems(recycler, state, true);
}
/**
* 布局结束
*
* @param state State
*/
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
if (state.isPreLayout()) return;
// 页面状态回调
setPageCount(getTotalPageCount());
setPageIndex(getPageIndexByOffset(), false);
}
/**
* 回收和填充布局
*
* @param recycler Recycler
* @param state State
* @param isStart 是否从头开始用于控制View遍历方向true 为从头到尾false 为从尾到头
*/
@SuppressLint("CheckResult")
private void recycleAndFillItems(RecyclerView.Recycler recycler, RecyclerView.State state,
boolean isStart) {
if (state.isPreLayout()) {
return;
}
Logi("mOffsetX = " + mOffsetX);
Logi("mOffsetY = " + mOffsetY);
// 计算显示区域区前后多存储一列或则一行
Rect displayRect = new Rect(mOffsetX - mItemWidth, mOffsetY - mItemHeight,
getUsableWidth() + mOffsetX + mItemWidth, getUsableHeight() + mOffsetY + mItemHeight);
// 对显显示区域进行修正(计算当前显示区域和最大显示区域对交集)
displayRect.intersect(0, 0, mMaxScrollX + getUsableWidth(), mMaxScrollY + getUsableHeight());
Loge("displayRect = " + displayRect.toString());
int startPos = 0; // 获取第一个条目的Pos
int pageIndex = getPageIndexByOffset();
startPos = pageIndex * mOnePageSize;
Logi("startPos = " + startPos);
startPos = startPos - mOnePageSize * 2;
if (startPos < 0) {
startPos = 0;
}
int stopPos = startPos + mOnePageSize * 4;
if (stopPos > getItemCount()) {
stopPos = getItemCount();
}
Loge("startPos = " + startPos);
Loge("stopPos = " + stopPos);
detachAndScrapAttachedViews(recycler); // 移除所有View
if (isStart) {
for (int i = startPos; i < stopPos; i++) {
addOrRemove(recycler, displayRect, i);
}
} else {
for (int i = stopPos - 1; i >= startPos; i--) {
addOrRemove(recycler, displayRect, i);
}
}
Loge("child count = " + getChildCount());
}
/**
* 添加或者移除条目
*
* @param recycler RecyclerView
* @param displayRect 显示区域
* @param i 条目下标
*/
private void addOrRemove(RecyclerView.Recycler recycler, Rect displayRect, int i) {
View child = recycler.getViewForPosition(i);
Rect rect = getItemFrameByPosition(i);
if (!Rect.intersects(displayRect, rect)) {
removeAndRecycleView(child, recycler); // 回收入暂存区
} else {
addView(child);
measureChildWithMargins(child, mWidthUsed, mHeightUsed);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
layoutDecorated(child,
rect.left - mOffsetX + lp.leftMargin + getPaddingLeft(),
rect.top - mOffsetY + lp.topMargin + getPaddingTop(),
rect.right - mOffsetX - lp.rightMargin + getPaddingLeft(),
rect.bottom - mOffsetY - lp.bottomMargin + getPaddingTop());
}
}
//--- 处理滚动 ----------------------------------------------------------------------------------
/**
* 水平滚动
*
* @param dx 滚动距离
* @param recycler 回收器
* @param state 滚动状态
* @return 实际滚动距离
*/
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State
state) {
int newX = mOffsetX + dx;
int result = dx;
if (newX > mMaxScrollX) {
result = mMaxScrollX - mOffsetX;
} else if (newX < 0) {
result = 0 - mOffsetX;
}
mOffsetX += result;
setPageIndex(getPageIndexByOffset(), true);
offsetChildrenHorizontal(-result);
if (result > 0) {
recycleAndFillItems(recycler, state, true);
} else {
recycleAndFillItems(recycler, state, false);
}
return result;
}
/**
* 垂直滚动
*
* @param dy 滚动距离
* @param recycler 回收器
* @param state 滚动状态
* @return 实际滚动距离
*/
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State
state) {
int newY = mOffsetY + dy;
int result = dy;
if (newY > mMaxScrollY) {
result = mMaxScrollY - mOffsetY;
} else if (newY < 0) {
result = 0 - mOffsetY;
}
mOffsetY += result;
setPageIndex(getPageIndexByOffset(), true);
offsetChildrenVertical(-result);
if (result > 0) {
recycleAndFillItems(recycler, state, true);
} else {
recycleAndFillItems(recycler, state, false);
}
return result;
}
/**
* 监听滚动状态滚动结束后通知当前选中的页面
*
* @param state 滚动状态
*/
@Override
public void onScrollStateChanged(int state) {
Logi("onScrollStateChanged = " + state);
mScrollState = state;
super.onScrollStateChanged(state);
if (state == SCROLL_STATE_IDLE) {
setPageIndex(getPageIndexByOffset(), false);
}
}
//--- 私有方法 ----------------------------------------------------------------------------------
/**
* 获取条目显示区域
*
* @param pos 位置下标
* @return 显示区域
*/
private Rect getItemFrameByPosition(int pos) {
Rect rect = mItemFrames.get(pos);
if (null == rect) {
rect = new Rect();
// 计算显示区域 Rect
// 1. 获取当前View所在页数
int page = pos / mOnePageSize;
// 2. 计算当前页数左上角的总偏移量
int offsetX = 0;
int offsetY = 0;
if (canScrollHorizontally()) {
offsetX += getUsableWidth() * page;
} else {
offsetY += getUsableHeight() * page;
}
// 3. 根据在当前页面中的位置确定具体偏移量
int pagePos = pos % mOnePageSize; // 在当前页面中是第几个
int row = pagePos / mColumns; // 获取所在行
int col = pagePos - (row * mColumns); // 获取所在列
offsetX += col * mItemWidth;
offsetY += row * mItemHeight;
// 状态输出用于调试
Logi("pagePos = " + pagePos);
Logi("行 = " + row);
Logi("列 = " + col);
Logi("offsetX = " + offsetX);
Logi("offsetY = " + offsetY);
rect.left = offsetX;
rect.top = offsetY;
rect.right = offsetX + mItemWidth;
rect.bottom = offsetY + mItemHeight;
// 存储
mItemFrames.put(pos, rect);
}
return rect;
}
/**
* 获取可用的宽度
*
* @return 宽度 - padding
*/
private int getUsableWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
/**
* 获取可用的高度
*
* @return 高度 - padding
*/
private int getUsableHeight() {
return getHeight() - getPaddingTop() - getPaddingBottom();
}
//--- 页面相关(私有) -----------------------------------------------------------------------------
/**
* 获取总页数
*/
private int getTotalPageCount() {
if (getItemCount() <= 0) return 0;
int totalCount = getItemCount() / mOnePageSize;
if (getItemCount() % mOnePageSize != 0) {
totalCount++;
}
return totalCount;
}
/**
* 根据pos获取该View所在的页面
*
* @param pos position
* @return 页面的页码
*/
private int getPageIndexByPos(int pos) {
return pos / mOnePageSize;
}
/**
* 根据 offset 获取页面Index
*
* @return 页面 Index
*/
private int getPageIndexByOffset() {
int pageIndex;
if (canScrollVertically()) {
int pageHeight = getUsableHeight();
if (mOffsetY <= 0 || pageHeight <= 0) {
pageIndex = 0;
} else {
pageIndex = mOffsetY / pageHeight;
if (mOffsetY % pageHeight > pageHeight / 2) {
pageIndex++;
}
}
} else {
int pageWidth = getUsableWidth();
if (mOffsetX <= 0 || pageWidth <= 0) {
pageIndex = 0;
} else {
pageIndex = mOffsetX / pageWidth;
if (mOffsetX % pageWidth > pageWidth / 2) {
pageIndex++;
}
}
}
Logi("getPageIndexByOffset pageIndex = " + pageIndex);
return pageIndex;
}
//--- 公开方法 ----------------------------------------------------------------------------------
/**
* 创建默认布局参数
*
* @return 默认布局参数
*/
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
/**
* 处理测量逻辑
*
* @param recycler RecyclerView
* @param state 状态
* @param widthMeasureSpec 宽度属性
* @param heightMeasureSpec 高估属性
*/
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(recycler, state, widthMeasureSpec, heightMeasureSpec);
int widthsize = View.MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
int widthmode = View.MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
int heightsize = View.MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
int heightmode = View.MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
// wrap_content 转换为 match_parent
if (widthmode != EXACTLY && widthsize > 0) {
widthmode = EXACTLY;
}
if (heightmode != EXACTLY && heightsize > 0) {
heightmode = EXACTLY;
}
setMeasuredDimension(View.MeasureSpec.makeMeasureSpec(widthsize, widthmode),
View.MeasureSpec.makeMeasureSpec(heightsize, heightmode));
}
/**
* 是否可以水平滚动
*
* @return true false 不是
*/
@Override
public boolean canScrollHorizontally() {
return mOrientation == HORIZONTAL;
}
/**
* 是否可以垂直滚动
*
* @return true false 不是
*/
@Override
public boolean canScrollVertically() {
return mOrientation == VERTICAL;
}
/**
* 找到下一页第一个条目的位置
*
* @return 第一个搞条目的位置
*/
int findNextPageFirstPos() {
int page = mLastPageIndex;
page++;
if (page >= getTotalPageCount()) {
page = getTotalPageCount() - 1;
}
Loge("computeScrollVectorForPosition next = " + page);
return page * mOnePageSize;
}
/**
* 找到上一页的第一个条目的位置
*
* @return 第一个条目的位置
*/
int findPrePageFirstPos() {
// 在获取时由于前一页的View预加载出来了所以获取到的直接就是前一页
int page = mLastPageIndex;
page--;
Loge("computeScrollVectorForPosition pre = " + page);
if (page < 0) {
page = 0;
}
Loge("computeScrollVectorForPosition pre = " + page);
return page * mOnePageSize;
}
/**
* 获取当前 X 轴偏移量
*
* @return X 轴偏移量
*/
public int getOffsetX() {
return mOffsetX;
}
/**
* 获取当前 Y 轴偏移量
*
* @return Y 轴偏移量
*/
public int getOffsetY() {
return mOffsetY;
}
//--- 页面对齐 ----------------------------------------------------------------------------------
/**
* 计算到目标位置需要滚动的距离{@link RecyclerView.SmoothScroller.ScrollVectorProvider}
*
* @param targetPosition 目标控件
* @return 需要滚动的距离
*/
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
PointF vector = new PointF();
int[] pos = getSnapOffset(targetPosition);
vector.x = pos[0];
vector.y = pos[1];
return vector;
}
/**
* 获取偏移量(为PagerGridSnapHelper准备)
* 用于分页滚动确定需要滚动的距离
* {@link PagerGridSnapHelper}
*
* @param targetPosition 条目下标
*/
int[] getSnapOffset(int targetPosition) {
int[] offset = new int[2];
int[] pos = getPageLeftTopByPosition(targetPosition);
offset[0] = pos[0] - mOffsetX;
offset[1] = pos[1] - mOffsetY;
return offset;
}
/**
* 根据条目下标获取该条目所在页面的左上角位置
*
* @param pos 条目下标
* @return 左上角位置
*/
private int[] getPageLeftTopByPosition(int pos) {
int[] leftTop = new int[2];
int page = getPageIndexByPos(pos);
if (canScrollHorizontally()) {
leftTop[0] = page * getUsableWidth();
leftTop[1] = 0;
} else {
leftTop[0] = 0;
leftTop[1] = page * getUsableHeight();
}
return leftTop;
}
/**
* 获取需要对齐的View
*
* @return 需要对齐的View
*/
public View findSnapView() {
if (null != getFocusedChild()) {
return getFocusedChild();
}
if (getChildCount() <= 0) {
return null;
}
int targetPos = getPageIndexByOffset() * mOnePageSize; // 目标Pos
for (int i = 0; i < getChildCount(); i++) {
int childPos = getPosition(getChildAt(i));
if (childPos == targetPos) {
return getChildAt(i);
}
}
return getChildAt(0);
}
//--- 处理页码变化 -------------------------------------------------------------------------------
private boolean mChangeSelectInScrolling = true; // 是否在滚动过程中对页面变化回调
private int mLastPageCount = -1; // 上次页面总数
private int mLastPageIndex = -1; // 上次页面下标
/**
* 设置页面总数
*
* @param pageCount 页面总数
*/
private void setPageCount(int pageCount) {
if (pageCount >= 0) {
if (mPageListener != null && pageCount != mLastPageCount) {
mPageListener.onPageSizeChanged(pageCount);
}
mLastPageCount = pageCount;
}
}
/**
* 设置当前选中页面
*
* @param pageIndex 页面下标
* @param isScrolling 是否处于滚动状态
*/
private void setPageIndex(int pageIndex, boolean isScrolling) {
Loge("setPageIndex = " + pageIndex + ":" + isScrolling);
if (pageIndex == mLastPageIndex) return;
// 如果允许连续滚动那么在滚动过程中就会更新页码记录
if (isAllowContinuousScroll()) {
mLastPageIndex = pageIndex;
} else {
// 否则只有等滚动停下时才会更新页码记录
if (!isScrolling) {
mLastPageIndex = pageIndex;
}
}
if (isScrolling && !mChangeSelectInScrolling) return;
if (pageIndex >= 0) {
if (null != mPageListener) {
mPageListener.onPageSelect(pageIndex);
}
}
}
/**
* 设置是否在滚动状态更新选中页码
*
* @param changeSelectInScrolling true更新false不更新
*/
public void setChangeSelectInScrolling(boolean changeSelectInScrolling) {
mChangeSelectInScrolling = changeSelectInScrolling;
}
/**
* 设置滚动方向
*
* @param orientation 滚动方向
* @return 最终的滚动方向
*/
@OrientationType
public int setOrientationType(@OrientationType int orientation) {
if (mOrientation == orientation || mScrollState != SCROLL_STATE_IDLE) return mOrientation;
mOrientation = orientation;
mItemFrames.clear();
int x = mOffsetX;
int y = mOffsetY;
mOffsetX = y / getUsableHeight() * getUsableWidth();
mOffsetY = x / getUsableWidth() * getUsableHeight();
int mx = mMaxScrollX;
int my = mMaxScrollY;
mMaxScrollX = my / getUsableHeight() * getUsableWidth();
mMaxScrollY = mx / getUsableWidth() * getUsableHeight();
return mOrientation;
}
//--- 滚动到指定位置 -----------------------------------------------------------------------------
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
int targetPageIndex = getPageIndexByPos(position);
smoothScrollToPage(targetPageIndex);
}
/**
* 平滑滚动到上一页
*/
public void smoothPrePage() {
smoothScrollToPage(getPageIndexByOffset() - 1);
}
/**
* 平滑滚动到下一页
*/
public void smoothNextPage() {
smoothScrollToPage(getPageIndexByOffset() + 1);
}
/**
* 平滑滚动到指定页面
*
* @param pageIndex 页面下标
*/
public void smoothScrollToPage(int pageIndex) {
if (pageIndex < 0 || pageIndex >= mLastPageCount) {
Log.e(TAG, "pageIndex is outOfIndex, must in [0, " + mLastPageCount + ").");
return;
}
if (null == mRecyclerView) {
Log.e(TAG, "RecyclerView Not Found!");
return;
}
// 如果滚动到页面之间距离过大先直接滚动到目标页面到临近页面在使用 smoothScroll 最终滚动到目标
// 否则在滚动距离很大时会导致滚动耗费的时间非常长
int currentPageIndex = getPageIndexByOffset();
if (Math.abs(pageIndex - currentPageIndex) > 3) {
if (pageIndex > currentPageIndex) {
scrollToPage(pageIndex - 3);
} else if (pageIndex < currentPageIndex) {
scrollToPage(pageIndex + 3);
}
}
// 具体执行滚动
LinearSmoothScroller smoothScroller = new PagerGridSmoothScroller(mRecyclerView);
int position = pageIndex * mOnePageSize;
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
//=== 直接滚动 ===
@Override
public void scrollToPosition(int position) {
int pageIndex = getPageIndexByPos(position);
scrollToPage(pageIndex);
}
/**
* 上一页
*/
public void prePage() {
scrollToPage(getPageIndexByOffset() - 1);
}
/**
* 下一页
*/
public void nextPage() {
scrollToPage(getPageIndexByOffset() + 1);
}
/**
* 滚动到指定页面
*
* @param pageIndex 页面下标
*/
public void scrollToPage(int pageIndex) {
if (pageIndex < 0 || pageIndex >= mLastPageCount) {
Log.e(TAG, "pageIndex = " + pageIndex + " is out of bounds, mast in [0, " + mLastPageCount + ")");
return;
}
if (null == mRecyclerView) {
Log.e(TAG, "RecyclerView Not Found!");
return;
}
int mTargetOffsetXBy = 0;
int mTargetOffsetYBy = 0;
if (canScrollVertically()) {
mTargetOffsetXBy = 0;
mTargetOffsetYBy = pageIndex * getUsableHeight() - mOffsetY;
} else {
mTargetOffsetXBy = pageIndex * getUsableWidth() - mOffsetX;
mTargetOffsetYBy = 0;
}
Loge("mTargetOffsetXBy = " + mTargetOffsetXBy);
Loge("mTargetOffsetYBy = " + mTargetOffsetYBy);
mRecyclerView.scrollBy(mTargetOffsetXBy, mTargetOffsetYBy);
setPageIndex(pageIndex, false);
}
/**
* 是否允许连续滚动默认为允许
*
* @return true 允许 false 不允许
*/
public boolean isAllowContinuousScroll() {
return mAllowContinuousScroll;
}
/**
* 设置是否允许连续滚动
*
* @param allowContinuousScroll true 允许false 不允许
*/
public void setAllowContinuousScroll(boolean allowContinuousScroll) {
mAllowContinuousScroll = allowContinuousScroll;
}
//--- 对外接口 ----------------------------------------------------------------------------------
private PageListener mPageListener = null;
public void setPageListener(PageListener pageListener) {
mPageListener = pageListener;
}
public interface PageListener {
/**
* 页面总数量变化
*
* @param pageSize 页面总数
*/
void onPageSizeChanged(int pageSize);
/**
* 页面被选中
*
* @param pageIndex 选中的页面
*/
void onPageSelect(int pageIndex);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2018 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2018-04-09 23:56:59
*
* GitHub: https://github.com/GcsSloop
* WeiBo: http://weibo.com/GcsSloop
* WebSite: http://www.gcssloop.com
*/
package com.shayu.onetoone.widget;
import android.util.DisplayMetrics;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import static com.shayu.onetoone.widget.PagerConfig.Logi;
/**
* 作用用于处理平滑滚动
* 作者GcsSloop
* 摘要用于用户手指抬起后页面对齐或者 Fling 事件
*/
public class PagerGridSmoothScroller extends LinearSmoothScroller {
private RecyclerView mRecyclerView;
public PagerGridSmoothScroller(@NonNull RecyclerView recyclerView) {
super(recyclerView.getContext());
mRecyclerView = recyclerView;
}
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
RecyclerView.LayoutManager manager = mRecyclerView.getLayoutManager();
if (null == manager) return;
if (manager instanceof PagerGridLayoutManager) {
PagerGridLayoutManager layoutManager = (PagerGridLayoutManager) manager;
int pos = mRecyclerView.getChildAdapterPosition(targetView);
int[] snapDistances = layoutManager.getSnapOffset(pos);
final int dx = snapDistances[0];
final int dy = snapDistances[1];
Logi("dx = " + dx);
Logi("dy = " + dy);
final int time = calculateTimeForScrolling(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, mDecelerateInterpolator);
}
}
}
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return PagerConfig.getMillisecondsPreInch() / displayMetrics.densityDpi;
}
}

View File

@ -0,0 +1,203 @@
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-09-20 16:32:43
*
* GitHub: https://github.com/GcsSloop
* WeiBo: http://weibo.com/GcsSloop
* WebSite: http://www.gcssloop.com
*/
package com.shayu.onetoone.widget;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SnapHelper;
import static com.shayu.onetoone.widget.PagerConfig.Loge;
/**
* 作用分页居中工具
* 作者GcsSloop
* 摘要每次只滚动一个页面
*/
public class PagerGridSnapHelper extends SnapHelper {
private RecyclerView mRecyclerView; // RecyclerView
/**
* 用于将滚动工具和 Recycler 绑定
*
* @param recyclerView RecyclerView
* @throws IllegalStateException 状态异常
*/
@Override
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws
IllegalStateException {
super.attachToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
/**
* 计算需要滚动的向量用于页面自动回滚对齐
*
* @param layoutManager 布局管理器
* @param targetView 目标控件
* @return 需要滚动的距离
*/
@Nullable
@Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull View targetView) {
int pos = layoutManager.getPosition(targetView);
Loge("findTargetSnapPosition, pos = " + pos);
int[] offset = new int[2];
if (layoutManager instanceof PagerGridLayoutManager) {
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
offset = manager.getSnapOffset(pos);
}
return offset;
}
/**
* 获得需要对齐的View对于分页布局来说就是页面第一个
*
* @param layoutManager 布局管理器
* @return 目标控件
*/
@Nullable
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager instanceof PagerGridLayoutManager) {
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
return manager.findSnapView();
}
return null;
}
/**
* 获取目标控件的位置下标
* (获取滚动后第一个View的下标)
*
* @param layoutManager 布局管理器
* @param velocityX X 轴滚动速率
* @param velocityY Y 轴滚动速率
* @return 目标控件的下标
*/
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager,
int velocityX, int velocityY) {
int target = RecyclerView.NO_POSITION;
Loge("findTargetSnapPosition, velocityX = " + velocityX + ", velocityY" + velocityY);
if (null != layoutManager && layoutManager instanceof PagerGridLayoutManager) {
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
if (manager.canScrollHorizontally()) {
if (velocityX > PagerConfig.getFlingThreshold()) {
target = manager.findNextPageFirstPos();
} else if (velocityX < -PagerConfig.getFlingThreshold()) {
target = manager.findPrePageFirstPos();
}
} else if (manager.canScrollVertically()) {
if (velocityY > PagerConfig.getFlingThreshold()) {
target = manager.findNextPageFirstPos();
} else if (velocityY < -PagerConfig.getFlingThreshold()) {
target = manager.findPrePageFirstPos();
}
}
}
Loge("findTargetSnapPosition, target = " + target);
return target;
}
/**
* 一扔(快速滚动)
*
* @param velocityX X 轴滚动速率
* @param velocityY Y 轴滚动速率
* @return 是否消费该事件
*/
@Override
public boolean onFling(int velocityX, int velocityY) {
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
int minFlingVelocity = PagerConfig.getFlingThreshold();
Loge("minFlingVelocity = " + minFlingVelocity);
return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
&& snapFromFling(layoutManager, velocityX, velocityY);
}
/**
* 快速滚动的具体处理方案
*
* @param layoutManager 布局管理器
* @param velocityX X 轴滚动速率
* @param velocityY Y 轴滚动速率
* @return 是否消费该事件
*/
private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return false;
}
RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
if (smoothScroller == null) {
return false;
}
int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
if (targetPosition == RecyclerView.NO_POSITION) {
return false;
}
smoothScroller.setTargetPosition(targetPosition);
layoutManager.startSmoothScroll(smoothScroller);
return true;
}
/**
* 通过自定义 LinearSmoothScroller 来控制速度
*
* @param layoutManager 布局故哪里去
* @return 自定义 LinearSmoothScroller
*/
protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return null;
}
return new PagerGridSmoothScroller(mRecyclerView);
}
//--- 公开方法 ----------------------------------------------------------------------------------
/**
* 设置滚动阀值
* @param threshold 滚动阀值
*/
public void setFlingThreshold(int threshold) {
PagerConfig.setFlingThreshold(threshold);
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="4"
tools:listitem="@layout/item_gift" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -17,7 +17,7 @@
<ImageView <ImageView
android:id="@+id/gift_icon" android:id="@+id/gift_icon"
android:layout_width="wrap_content" android:layout_width="30dp"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -160,7 +160,7 @@
android:src="@mipmap/ic_msg_add" /> android:src="@mipmap/ic_msg_add" />
</LinearLayout> </LinearLayout>
<View <LinearLayout
android:id="@+id/audio_layout" android:id="@+id/audio_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="190dp" android:layout_height="190dp"
@ -168,5 +168,6 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" /> tools:visibility="visible"
android:orientation="horizontal" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,28 +2,34 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:background="@color/white"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="190dp"> android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/gift_list" android:id="@+id/gift_list"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="165dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/bottom_layout"
tools:listitem="@layout/item_gift"
app:spanCount="4"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
app:spanCount="4"
tools:listitem="@layout/item_gift" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bottom_layout" android:id="@+id/bottom_layout"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="22dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/gift_list">
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -45,9 +51,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:text="139999992"
android:textColor="#999999" android:textColor="#999999"
android:textSize="12sp" android:textSize="12sp" />
tools:text="139999992" />
<Button <Button
android:id="@+id/top_up_btn" android:id="@+id/top_up_btn"
@ -56,9 +62,9 @@
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:background="@drawable/bg_gift_top_up" android:background="@drawable/bg_gift_top_up"
android:text="充值"
android:textColor="#FFB554" android:textColor="#FFB554"
android:textSize="10sp" android:textSize="10sp" />
tools:text="充值" />
</LinearLayout> </LinearLayout>
<Button <Button
@ -66,12 +72,12 @@
android:layout_width="51dp" android:layout_width="51dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="@drawable/bg_button" android:background="@drawable/bg_button"
android:text="發送"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
tools:text="發送" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>