update
This commit is contained in:
parent
da6ca4fbbb
commit
6d495d11fd
@ -6,6 +6,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@ -15,25 +16,32 @@ import androidx.fragment.app.Fragment;
|
||||
*/
|
||||
public abstract class BaseFragment extends Fragment {
|
||||
public Context mContext;
|
||||
|
||||
private View itemView;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View itemView = createView(inflater, container, savedInstanceState);
|
||||
this.mContext = getContext();
|
||||
this.itemView = itemView;
|
||||
initView(itemView);
|
||||
return itemView;
|
||||
}
|
||||
|
||||
public <T extends View> T findViewById(@IdRes int id) {
|
||||
return itemView.findViewById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化页面
|
||||
*
|
||||
* @param itemView 布局view
|
||||
*/
|
||||
public abstract void initView(View itemView);
|
||||
|
||||
/**
|
||||
* 创建页面
|
||||
*
|
||||
* @return 布局
|
||||
*/
|
||||
public abstract View createView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -30,6 +30,8 @@ import com.shayu.onetoone.listener.OnDialogClickListener;
|
||||
import com.shayu.onetoone.listener.OnSendMessageListener;
|
||||
import com.shayu.onetoone.manager.OTONetManager;
|
||||
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.glide.ImgLoader;
|
||||
import com.yunbao.common.http.base.HttpCallback;
|
||||
@ -67,12 +69,6 @@ public class ChatMessageFragment extends AbsConversationFragment {
|
||||
ProcessImageUtil cameraUtil;
|
||||
String targetId;
|
||||
Conversation.ConversationType conversationType = Conversation.ConversationType.PRIVATE;
|
||||
ViewGroup audioLayout;
|
||||
ImageView btnAudio;
|
||||
ImageView btnText;
|
||||
ImageView btnClose;
|
||||
private float mLastTouchY;
|
||||
private boolean mUpDirection;
|
||||
|
||||
|
||||
RoundedImageView avatar;
|
||||
@ -88,9 +84,9 @@ public class ChatMessageFragment extends AbsConversationFragment {
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
targetId = getActivity().getIntent().getStringExtra("targetId");
|
||||
initBtn();
|
||||
initChat();
|
||||
targetId = getActivity().getIntent().getStringExtra("targetId");
|
||||
cameraUtil = new ProcessImageUtil(getActivity(), "com.shayu.onetoone.fileprovider");
|
||||
|
||||
mRongExtension.setVisibility(View.VISIBLE);
|
||||
@ -176,114 +172,32 @@ public class ChatMessageFragment extends AbsConversationFragment {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
MsgInputPanelForAudio audio;
|
||||
MsgInputPanelForGift giftPanel;
|
||||
|
||||
private void initBtn() {
|
||||
try {
|
||||
Field field = mRongExtension.getInputPanel().getClass().getDeclaredField("mInputPanel");
|
||||
field.setAccessible(true);
|
||||
mInputPanel = (View) field.get(mRongExtension.getInputPanel());
|
||||
assert mInputPanel != null;
|
||||
mSendBtn = mInputPanel.findViewById(R.id.send_btn);
|
||||
ImageView mVoiceToggleBtn = (ImageView) mInputPanel.findViewById(R.id.input_panel_voice_toggle);
|
||||
mSendBtn = mInputPanel.getRootView().findViewById(R.id.send_btn);
|
||||
|
||||
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;
|
||||
|
||||
mVoiceToggleBtn.setOnClickListener(v -> {
|
||||
SendMessageManager.sendMessageForAudio(targetId, new OnSendMessageListener() {
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
audio = new MsgInputPanelForAudio(targetId, getActivity(), mRongExtension);
|
||||
giftPanel = new MsgInputPanelForGift(targetId, getActivity(), mRongExtension);
|
||||
mVoiceToggleBtn.setOnClickListener(view -> {
|
||||
audio.show();
|
||||
});
|
||||
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 -> {
|
||||
new GiftDialog(mContext).showDialog();
|
||||
giftPanel.show();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
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() {
|
||||
Conversation.ConversationType conversationType = Conversation.ConversationType.PRIVATE;
|
||||
|
@ -14,14 +14,17 @@ import com.shayu.onetoone.R;
|
||||
import com.shayu.onetoone.bean.GiftBean;
|
||||
import com.yunbao.common.glide.ImgLoader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GiftListAdapter extends RecyclerView.Adapter<GiftListAdapter.ViewHolder> {
|
||||
private Context mContext;
|
||||
private List<GiftBean> list;
|
||||
private int selectPosition = 0;
|
||||
|
||||
public GiftListAdapter(Context mContext) {
|
||||
this.mContext = mContext;
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void setList(List<GiftBean> list) {
|
||||
@ -45,7 +48,7 @@ public class GiftListAdapter extends RecyclerView.Adapter<GiftListAdapter.ViewHo
|
||||
return list.size();
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
private ImageView giftIcon;
|
||||
private TextView giftName;
|
||||
private TextView money;
|
||||
@ -56,9 +59,10 @@ public class GiftListAdapter extends RecyclerView.Adapter<GiftListAdapter.ViewHo
|
||||
giftIcon = itemView.findViewById(R.id.gift_icon);
|
||||
giftName = itemView.findViewById(R.id.gift_name);
|
||||
money = itemView.findViewById(R.id.money);
|
||||
select.findViewById(R.id.bg_select);
|
||||
select = itemView.findViewById(R.id.bg_select);
|
||||
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() + "");
|
||||
ImgLoader.display(itemView.getContext(), giftBean.getGifticon(), giftIcon);
|
||||
select.setVisibility(View.GONE);
|
||||
|
||||
if (position == selectPosition) {
|
||||
select.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
select.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,13 @@ 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.dialog.AbsDialogPopupWindow;
|
||||
import com.yunbao.common.http.base.HttpCallback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GiftDialog extends AbsDialogPopupWindow {
|
||||
@ -46,8 +50,13 @@ public class GiftDialog extends AbsDialogPopupWindow {
|
||||
money = findViewById(R.id.money);
|
||||
topUpBtn = findViewById(R.id.top_up_btn);
|
||||
sendBtn = 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();
|
||||
}
|
||||
@ -57,7 +66,11 @@ public class GiftDialog extends AbsDialogPopupWindow {
|
||||
.getGiftList(new HttpCallback<List<GiftBean>>() {
|
||||
@Override
|
||||
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
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
19
OneToOne/src/main/res/layout/fragment_gift.xml
Normal file
19
OneToOne/src/main/res/layout/fragment_gift.xml
Normal 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>
|
@ -17,7 +17,7 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gift_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -160,7 +160,7 @@
|
||||
android:src="@mipmap/ic_msg_add" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
<LinearLayout
|
||||
android:id="@+id/audio_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="190dp"
|
||||
@ -168,5 +168,6 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
tools:visibility="visible"
|
||||
android:orientation="horizontal" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -2,28 +2,34 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="@color/white"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="190dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gift_list"
|
||||
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:layout_constraintBottom_toTopOf="@+id/bottom_layout"
|
||||
tools:listitem="@layout/item_gift"
|
||||
app:spanCount="4"
|
||||
app:layout_constraintEnd_toEndOf="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
|
||||
android:id="@+id/bottom_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="22dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/gift_list">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@ -45,9 +51,9 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="139999992"
|
||||
android:textColor="#999999"
|
||||
android:textSize="12sp"
|
||||
tools:text="139999992" />
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/top_up_btn"
|
||||
@ -56,9 +62,9 @@
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="6dp"
|
||||
android:background="@drawable/bg_gift_top_up"
|
||||
android:text="充值"
|
||||
android:textColor="#FFB554"
|
||||
android:textSize="10sp"
|
||||
tools:text="充值" />
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
@ -66,12 +72,12 @@
|
||||
android:layout_width="51dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/bg_button"
|
||||
android:text="發送"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="發送" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
x
Reference in New Issue
Block a user