可以看到很简单,这是一个抽象类,那么具体的View的展示需要大家通过复写getView,用法和ListView及其类似,同时我们提供了notifyDataChanged()的方法,当你的数据集发生变化的时候,你可以调用该方法,UI会自动刷新。
当然,仅仅有了Adapter是不行的,我们需要添加相应的代码对其进行支持。
(2)TagFlowLayout对Adapter的支持
那么最主要就是提供一个setAdapter的方法:
public void setAdapter(TagAdapter adapter)
{
mTagAdapter = adapter;
mTagAdapter.setOnDataChangedListener(this);
changeAdapter();
}
private void changeAdapter()
{
removeAllViews();
TagAdapter adapter = mTagAdapter;
TagView tagViewContainer = null;
for (int i = 0; i < adapter.getCount(); i++)
{
View tagView = adapter.getView(this, i, adapter.getItem(i));
tagView.setDuplicateParentStateEnabled(true);
tagViewContainer.setLayoutParams(tagView.getLayoutParams());
tagViewContainer.addView(tagView);
addView(tagViewContainer);
}
}
@Override
public void onChanged()
{
changeAdapter();
}
ok,可以看到当你调用setAdapter进来,首先我们会注册mTagAdapter.setOnDataChangedListener
这个回调,主要是用于响应notifyDataSetChanged()
。然后进入changeAdapter方法
,在这里首先移除所有的子View,然后根据mAdapter.getView的返回,开始逐个构造子View,然后进行添加。
这里注意下:我们的上述的代码,对mAdapter.getView返回的View,外围报了一层TagView,这里暂时不要去想,我们后面会细说。
到此,我们的Adapter添加完毕。
三、支持onTagClickListener
ok,这个接口也非常重要,当然我私下了解了下,很多同学都加上了,但是基本都是对单个标签View去setOnClickListener,然后去比对Tag确定点击的是哪个标签,最后回调出来。当然,我们这里考虑一种更优雅的方式:
我们从父控件下手,当我们确定用户点击在我们的TagFlowLayout上时,我们根据用户点击的坐标,看看是否点击的是我们的某个View,然后进行click回调。是不是有点像事件分发,哈,我们这里可以称为点击分发。
那么,我们需要关注的就是onTouchEvent
和performClick
方法。
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_UP)
{
mMotionEvent = MotionEvent.obtain(event);
}
return super.onTouchEvent(event);
}
@Override
public boolean performClick()
{
if (mMotionEvent == null) return super.performClick();
int x = (int) mMotionEvent.getX();
int y = (int) mMotionEvent.getY();
mMotionEvent = null;
TagView child = findChild(x, y);
int pos = findPosByView(child);
if (child != null)
{
doSelect(child, pos);
if (mOnTagClickListener != null)
{
return mOnTagClickListener.onTagClick(child.getTagView(), pos, this);
}
}
return super.performClick();
}
private TagView findChild(int x, int y)
{
final int cCount = getChildCount();
for (int i = 0; i < cCount; i++)
{
TagView v = (TagView) getChildAt(i);
if (v.getVisibility() == View.GONE) continue;
Rect outRect = new Rect();
v.getHitRect(outRect);
if (outRect.contains(x, y))
{
return v;
}
}
return null;
}
可以看到我们这里巧妙的利用了performClick
这个回调,来确定的确是触发了click事件,而不是自己去判断什么算click的条件。但是呢,我们的performClick
没有提供MotionEvent的参数,不过不要紧,我们都清楚click的事件发生在ACTION_UP之后,所以我们提供一个变量去记录最后一次触发ACTION_UP的mMotionEvent。
我们在performClick
里面,根据mMotionEvent,去查找是否落在某个子View身上,如果落在,那么就确定点击在它身上了,直接回调即可,关于接口的定义如下,(ps:关于doSelect方法,我们后面说):
public interface OnTagClickListener
{
boolean onTagClick(View view, int position, FlowLayout parent);
}
private OnTagClickListener mOnTagClickListener;
public void setOnTagClickListener(OnTagClickListener onTagClickListener)
{
mOnTagClickListener = onTagClickListener;
if (onTagClickListener != null) setClickable(true);
}
可以看到,如果设置了setOnTagClickListener
,我们显示的设置了父ViewsetClickable(true)
。以防万一父View不具备消费事件的能力。
四、全面支持Checked
这一节呢,主要包含支持几个功能:
* 直接设置selector为background即可完成标签选则的切换,类似CheckBox
* 支持控制选择的Tag数量,比如:单选、多选
* 支持setOnSelectListener,当选择某个Tag后回调
首先,我们提供了两个自定义的属性,multi_suppout
和max_select
。一个是指出是否支持选择(如果为false,意味着你只能通过setOnTagClickListener去做一些操作),一个是设置最大的选择数量,-1为不限制数量。
ok,那么核心的代码依然在performClick中被调用的:
@Override
public boolean performClick()
{
//省略了一些代码…
doSelect(child, pos);
if (mOnTagClickListener != null)
{
return mOnTagClickListener.onTagClick(child.getTagView(), pos, this);
}
//省略了一些代码…
}
private void doSelect(TagView child, int position)
{
if (mSupportMulSelected)
{
if (!child.isChecked())
{
if (mSelectedMax > 0 && mSelectedView.size() >= mSelectedMax)
return;
child.setChecked(true);
mSelectedView.add(position);
} else
{
child.setChecked(false);
mSelectedView.remove(position);
}
if (mOnSelectListener != null)
{
mOnSelectListener.onSelected(new HashSet(mSelectedView));
}
}
}
ok,可以看到,如果点击了某个标签,进入doSelect方法,首先判断你是否开启了多选的支持(默认支持),然后判断当前的View是否是非Checked
的状态,如果是非Checked
状态,则判断最大的选择数量,如果没有达到,则设置checked=true,同时加入已选择的集合;反之已经是checked状态,就是取消选择状态了。同时如果设置了mOnSelectListener,回调一下。
ok,其实这里隐藏了一些东西,关于接口回调我们不多赘述了,大家都明白。这里主要看Checked。首先你肯定有几个问题:
-
childView哪来的isChecked(),setChecked()方法?
-
这么做就能改变UI了?
下面我一一解答:首先,我们并非知道adapter#getView返回的是什么View,但是可以肯定的是,大部分View都是没有isChecked(),setChecked()方法的。但是我们需要有,怎么做?还记得我们setAdapter的时候,给getView外层包了一层TagView么,没错,就是TagView起到的作用:
package com.zhy.view.flowlayout;
import android.content.Context;
import android.view.View;
import android.widget.Checkable;
import android.widget.FrameLayout;
/**
- Created by zhy on 15/9/10.
*/
public class TagView extends FrameLayout implements Checkable
{
private boolean isChecked;
private static final int[] CHECK_STATE = new int[]{android.R.attr.state_checked};
public TagView(Context context)
{
super(context);
}
public View getTagView()
{
return getChildAt(0);
}
@Override
public int[] onCreateDrawableState(int extraSpace)
{
int[] states = super.onCreateDrawableState(extraSpace + 1);
if (isChecked())
{
mergeDrawableStates(states, CHECK_STATE);
}
return states;
}
/**
-
Change the checked state of the view
-
@param checked The new checked state
*/
@Override
public void setChecked(boolean checked)
{
if (this.isChecked != checked)
{
this.isChecked = checked;
refreshDrawableState();
}
}
/**
- @return The current checked state of the view
*/
@Override
public boolean isChecked()
{
return isChecked;
}
/**
- Change the checked state of the view to the inverse of its current state
*/
@Override
public void toggle()
{
setChecked(!isChecked);
}
}
我们的TagView实现了Checkable接口,所以提供了问题一的方法。
下面解释问题二: 这么做就能改变UI了?
我们继续看TagView这个类,这个类中我们复写了onCreateDrawableState
,在里面添加了CHECK_STATE
的支持。当我们调用setChecked方法的时候,我们会调用refreshDrawableState()
来更新我们的UI。
但是你可能又会问了,你这个是TagView支持了CHECKED状态,关它的子View什么事?我们的background可是设置在子View上的。
没错,这个问题问的相当好,你还记得我们在setAdapter,addView之前有一行非常核心的代码:#mAdapter.getView().setDuplicateParentStateEnabled(true);
,setDuplicateParentStateEnabled
这个方法允许我们的CHECKED状态向下传递。
到这,你应该明白了吧~~
所以我们对于UI的变化,只需要设置View的Backgroud为:
<?xml version="1.0" encoding="utf-8"?><item
android:drawable=“@drawable/checked_bg”
android:state_checked=“true”>
这样,如果你的设计稿发生变化,大部分情况下,你只需要改改xml文件就可以了。
ok,到此我们的核心部分的剖析就结束了,接下来贴贴用法:
五、用法
用法其实很简单,大家可以参考例子,我这里大致贴一下:
(1)设置数据
mFlowLayout.setAdapter(new TagAdapter(mVals)
{
@Override
public View getView(FlowLayout parent, int position, String s)
{
TextView tv = (TextView) mInflater.inflate(R.layout.tv,
mFlowLayout, false);
tv.setText(s);
return tv;
}
});
getView中回调,类似ListView等用法。
(2)对于选中状态
你还在复杂的写代码设置选中后标签的显示效果么,翔哥说No!
<?xml version="1.0" encoding="utf-8"?><item android:color=“@color/tag_select_textcolor”
android:drawable=“@drawable/checked_bg”
android:state_checked=“true”>
设置个background,上面一个状态为android:state_checked,另一个为正常。写写布局文件我都嫌慢,怎么能写一堆代码控制效果,设置改个效果,岂不是没时间dota了。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档
喜欢的朋友可以关注、转发、点赞 感谢!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
存中…(img-eRh7LjPR-1713728231125)]
[外链图片转存中…(img-EWnhGlLJ-1713728231125)]
[外链图片转存中…(img-Aua9gFIs-1713728231126)]
[外链图片转存中…(img-0n0E8OQl-1713728231127)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
[外链图片转存中…(img-5rvKKDTz-1713728231128)]
最后
考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档
[外链图片转存中…(img-c0pF3xbD-1713728231128)]
[外链图片转存中…(img-g7kKRLSW-1713728231129)]
喜欢的朋友可以关注、转发、点赞 感谢!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!