RecyclerView的item点击事件回调
@Override
public void onBindViewHolder(@NonNull Adapter2ViewHolder holder, int position) {
holder.mTextView.setTag(position);
holder.mTextView.setText("position = " + position);
holder.itemView.setOnClickListener(onItemClickListener);
}
上述在Adapter中回调item的点击事件到Actviity等,出现的问题如下。
1、存在问题
图一item横向滑动,会触发点击事件,图二,则不会,因为图二外层的viewpager把横向的事件给拦截了。
图一为什么会触发点击事件了?
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
return true;
}
return false;
}
当子child没有消费,最终会调用到view的onTouchEvent方法中,可以看到,只要clickable为true,无论是move还是down,最终都会返回true。
设置setItemClick给itemView的clickable为true,所以item得move事件,也会触发事件消费。
2、问题解决
和ViewPager处理类似,可以将横向的Move事件,滑动超过一定距离就不消费。
class HyRvItemFrameLayout : FrameLayout {
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
private val TAG = "HyRvItemFrameLayout"
var parentRecyclerView: RecyclerView? = null
var orientation = -1
var noDeal = false
private var mTouchSlop = 0
override fun onFinishInflate() {
super.onFinishInflate()
val vc = ViewConfiguration.get(context)
mTouchSlop = vc.scaledTouchSlop
}
init {
this.layoutParams =
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
fun setChildView(itemView: View, params: ViewGroup.LayoutParams? = null) {
if (params != null) {
this.layoutParams = params
}
this.addView(itemView)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (parentRecyclerView == null) {
parentRecyclerView = findHyRecyclerView(this)
if (parentRecyclerView?.layoutManager is LinearLayoutManager) {
orientation = (parentRecyclerView?.layoutManager as LinearLayoutManager)?.orientation ?: -1
}
}
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
return super.dispatchTouchEvent(ev)
}
private var initDownX = 0f
private var initDownY = 0f
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.actionMasked) {
MotionEvent.ACTION_DOWN -> {
initDownX = ev?.getX() ?: 0f
initDownY = ev?.getY() ?: 0f
noDeal = false
}
}
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent?): Boolean {
try {
when (ev?.actionMasked) {
MotionEvent.ACTION_DOWN -> {
initDownX = ev?.getX() ?: 0f
initDownY = ev?.getY() ?: 0f
}
MotionEvent.ACTION_MOVE -> {
var moveX = ev.getX()
var moveY = ev.getY()
val diffX = initDownX - moveX
val diffY = initDownY - moveY
if (orientation == LinearLayoutManager.HORIZONTAL && Math.abs(diffX) < Math.abs(diffY) && Math.abs(diffY) > mTouchSlop) {
Log.d(TAG, "onTouchEvent: 水平方向----- ")
noDeal = true
} else if (orientation == LinearLayoutManager.VERTICAL && Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > mTouchSlop) {
Log.d(TAG, "onTouchEvent: 竖直方向===== ")
noDeal = true
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
initDownY = 0f
initDownX = 0f
}
}
var flag = !noDeal && super.onTouchEvent(ev)
Log.d(TAG, "onTouchEvent: " + flag)
return flag
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
private fun findHyRecyclerView(viewGroup: ViewGroup): RecyclerView? {
var viewGroup: ViewGroup? = viewGroup
try {
val viewRoot = (context as FragmentActivity).findViewById<ViewGroup>(R.id.content)
while (viewGroup != null) {
if (viewGroup is RecyclerView) {
return viewGroup
}
if (viewGroup.parent == viewRoot) {
return null
}
// LogUtil.d(TAG, "findHyRecyclerView: " + viewGroup);
viewGroup = viewGroup.parent as ViewGroup
}
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
}
2.1、使用方式1,在xml中,将HyRvItemFrameLayout做为ViewHolder的itemView的根布局。
2.2、使用方式2
override fun onHyCreateViewHolder(parent: ViewGroup, viewType: Int): HomeLeftCircleViewHolder {
var view = LayoutInflater.from(mContext).inflate(R.layout.item_home_left_circle_list, parent, false)
Log.d(TAG, "onHyCreateViewHolder: " + view.layoutParams)
var itemFrameLayout = HyRvItemFrameLayout(mContext)
Log.d(TAG, "onHyCreateViewHolder: " + itemFrameLayout.layoutParams)
itemFrameLayout.addView(view)
Log.d(TAG, "onHyCreateViewHolder: " + view.layoutParams)
return HomeLeftCircleViewHolder(itemFrameLayout, parent)
}
注意事项:
第一个打印RecyclerView$LayoutParams,因为以RecyclerView为目标生成LayoutParams。
第二个是new的对象,还没添加到ViewGroup中,所以为null。
第三个添加第一个view,因为view的LayoutParams不是FrameLayoutParams,需要将params拷贝一份,生成FrameLayoutParams。
此时不会丢失View持有的RecyclerView的LayoutParams,因为默认的RecyclerView.layoutParams没有特有的参数。
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (sPreserveMarginParamsInLayoutParamConversion) {
if (lp instanceof LayoutParams) {
return new LayoutParams((LayoutParams) lp);
} else if (lp instanceof MarginLayoutParams) {
return new LayoutParams((MarginLayoutParams) lp);
}
}
return new LayoutParams(lp);
}
public MarginLayoutParams(MarginLayoutParams source) {
this.width = source.width;
this.height = source.height;
this.leftMargin = source.leftMargin;
this.topMargin = source.topMargin;
this.rightMargin = source.rightMargin;
this.bottomMargin = source.bottomMargin;
this.startMargin = source.startMargin;
this.endMargin = source.endMargin;
this.mMarginFlags = source.mMarginFlags;
}
当ViewHolder的item被添加到RecyclerView,会为itemFrameLayout生成LayoutParams
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
if (mLayout == null) {
throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel());
}
return mLayout.generateDefaultLayoutParams();
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
所以如果不给itemFrameLayout设置LayoutParams,则最终宽高都是Wrap_content,如果设置了LayoutParams,也会拷贝一份,生成RecyclerView.LayoutParams。
3、根布局选择
上面HyRvItemFrameLayout使用FrameLayout,分析是否可以RelativeLayout和LinearLayout。
针对三个根布局,分析三个布局每一次OnMeasure过程,每一个子View的onMeasure次数。
4、代码动态配置
通过配置,动态调用。第一个通过xml配置。第二个通过插件配置。
4.1、定义是否需要过滤掉Move。
<declare-styleable name="HyRvItemFrameLayout">
<attr name="fliter_click_move_event" format="boolean"></attr>
</declare-styleable>
4.2、在ViewHolder的item根布局xml中使用。
<?xml version="1.0" encoding="utf-8"?>
<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"
app:fliter_click_move_event="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
4.3、运行时,在BaseAdapter中获取定义的属性。
constructor(context: Context) : super() {
mContext = context
mInflater = LayoutInflater.from(mContext)
setFliterMoveValue()
}
fun setFliterMoveValue() {
if (mInflater.factory2 == null) {
mInflater.setFactory2(object : LayoutInflater.Factory2 {
override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
fliterMoveClick = attrs.getAttributeBooleanValue(
"http://schemas.android.com/apk/res-auto",
"fliter_click_move_event",
false
)
return null
}
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
return null
}
})
}
}
4.4、通过属性值,在BaseViewHolder中添加指定内容。
public HyBaseViewHolder(View itemView, ViewGroup parent) {
super(fliterMoveView(itemView));
findViews();
this.parent = parent;
}
private static View fliterMoveView(View itemView) {
if (fliterMove) {
var itemFrameLayout = new HyRvItemFrameLayout(itemView.getContext());
itemFrameLayout.addView(itemFrameLayout);
return itemFrameLayout;
}
return itemView;
}
5、通过plugin,在编译时生成4中的代码。
class ItemClickPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.tasks.register("mahao") {
println("执行了")
}
var configExtension = target.extensions.create("ClickConfig", AsmExtension::class.java)
var androidExtension = target.extensions.getByType(AndroidComponentsExtension::class.java)
androidExtension.onVariants { variant ->
variant.instrumentation?.transformClassesWith(
ItemClickTransform::class.java,
InstrumentationScope.ALL,
{
it.enable.set(configExtension.enable)
})
variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES)
}
}
}
5.1、找到这个类,isInstrumentable方法负责过滤所有的class。
abstract class ItemClickTransform : AsmClassVisitorFactory<ItemClickParams> {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
System.out.println(parameters.get().enable.get().toString() + " " + nextClassVisitor)
if (parameters.get().enable.get()) {
if (classContext.currentClassData.className.equals("com.mahao.customview.livedata.adapter.LiveDataAdapter2\$ClickFliter")) {
return CliclFliterVisitor(nextClassVisitor)
} else if (classContext.currentClassData.className.equals("com.mahao.customview.livedata.adapter.LiveDataAdapter2")) {
return ItemClickClassVisitor(nextClassVisitor)
} else if (classContext.currentClassData.className.equals("com.mahao.customview.livedata.adapter.viewholder.Adapter2ViewHolder")){
return ViewHolderClassVisitor(nextClassVisitor)
}
}
return TraceClassVisitor(nextClassVisitor, PrintWriter("trace"))
}
override fun isInstrumentable(classData: ClassData): Boolean {
System.out.println("transform = " + classData.className)
if (classData.className.equals("com.mahao.customview.livedata.adapter.LiveDataAdapter2") || classData.className.equals(
"com.mahao.customview.livedata.adapter.LiveDataAdapter2\$ClickFliter"
)
) {
return true
} else {
return false
}
}
}
5.2、找到这个方法
class ItemClickClassVisitor(var visitor: ClassVisitor) : ClassVisitor(Opcodes.ASM9, visitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
/*val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
if (name == "<clinit>" || name == "<init>") {
return methodVisitor
}*/
System.out.println("name == " + name + " " + descriptor)
// System.out.println(name.equals("onCreateViewHolder"))
System.out.println(descriptor?.equals("(Landroid/view/ViewGroup;I)Landroidx/recyclerview/widget/RecyclerView\$ViewHolder") == true)
if (name.equals("onCreateViewHolder") && descriptor?.startsWith("(Landroid/view/ViewGroup;I)") == true) {
System.out.println("执行到里面了啊")
val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
return ItemClickMethodVisitorAdapter(methodVisitor)
}
return super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
5.3、用命令生成调用的方法
class ItemClickMethodVisitorAdapter(var methodVisitor: MethodVisitor?) :
MethodVisitor(ASM9, methodVisitor) {
override fun visitCode() {
System.out.println("方法执行开始")
visitVarInsn(ALOAD, 0);
visitMethodInsn(INVOKEVIRTUAL, "com/mahao/customview/livedata/adapter/LiveDataAdapter2", "setFliterMoveValue", "()V", false);
super.visitCode()
}
}
5.4、最终结果
生成的文件路径CustomViewRefresh/app/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/目录下