Android自定义控件总结(一)
目的:收集和整理所有的Android自定义控件
文章目录
- Android自定义控件总结(一)
- 前言
- 一、面包屑布局(BreadCrumbView)
- 1.自定义BreadCrumbView,继承FrameLayout
- 2.使用自定义BreadCrumbView
- 3.使用效果
- 4.项目目录
- 二、树型结构布局(TreeListView)
- 1.创建自定义适配器TreeAdapter
- 2.使用自定义TreeAdapter
- 二、分页布局(jetpack的paging库)
- 1.定义适配器、数据接口Service、数据源
- 2.使用Paging分页布局
- 三、自定义底部导航栏(BottomNavigationView)
- 创建自定义工具类NavHelper
- 使用自定义适配器
- 四、自定义RecyclerView(单选、多选、长按删除)
- 01.列表样式的核心就是适配器
- 02.使用自定义适配器:
- 五、自定义RecyclerView(item的拖拽监听)
- 01.创建自定义适配器:
- 02.使用自定义适配器:
- 六、自定义RecyclerView下拉刷新
- 01.创建自定义适配器
- 02.使用自定义适配器
- 03.定义数据源
- 04.效果图
- 七、ListAdapter的使用
- 01.创建自定义适配器
- 02.使用自定义适配器
- 八、自定义提示框
- 01.布局文件
- 02.创建并使用自定义提示框(四种样式的提示框)
- 九、自定义列表对话框
- 01.创建列表的适配器,设置监听事件(监听事件在适配器中处理)
- 02.创建列表对话框
- 总结
前言
后续会不断添加自定义控件实例,希望做成一个Android自定义控件大全
一、面包屑布局(BreadCrumbView)
1.自定义BreadCrumbView,继承FrameLayout
01.定义变量参数(需要重点关注TabListener),创建构造函数:
//自定义BreadCrumbsView,继承FrameLayout
public class BreadCrumbsView extends FrameLayout {
private Context mContext;
private LinkedList<Tab> tabList = new LinkedList<>();
private RecyclerView recyclerView;
private TabAdapter tabAdapter;
private OnTabListener onTabListener;
public BreadCrumbsView(Context context) {
super(context);
init(context);
}
public BreadCrumbsView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BreadCrumbsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public BreadCrumbsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
......
02.定义初始化函数,进行传参(init()函数需要放到构造函数之中):
private void init(Context context) {
this.mContext = context;
View view = View.inflate(context, R.layout.layout_breadcrumbs_container, null);
recyclerView = view.findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
//设置每个item的TextView的最大宽度值
float maxWidth = (ScreenUtil.getScreenWidth(mContext) - ScreenUtil.dip2px(mContext, 32)) / 3.0f - ScreenUtil.dip2px(mContext, 8);
//为自定义View设置适配器,定义每个itemView的内容和样式
//TabAdapter的构造器的参数:context、tabList、点击事件
tabAdapter = new TabAdapter(mContext, tabList, new OnClickListener() {
@Override
public void onClick(View v) {
int index = (int) v.getTag();
//触发点击事件,自定义处理点击事件
selectAt(index);
}
}, maxWidth);
recyclerView.setAdapter(tabAdapter);
addView(view);
}
03.定义添加Tab的函数
//为面包屑布局的每一个itemView设置一个tab,记录itemView的状态、内容等数值
private void addTab(Tab tab) {
// 修改tab的状态
if (!tabList.isEmpty()) {
tabList.getLast().setCurrent(false);
}
// 设置新增tab的数据及状态
// 设置角标
tab.setIndex(getCurrentIndex() + 1);
// 设置状态
tab.setCurrent(true);
tabList.add(tab);
if (onTabListener != null) {
onTabListener.onAdded(tab);
}
tabAdapter.notifyDataSetChanged();
}
//添加新的面包屑
public void addTab(String content, Map<String, String> value) {
Tab tab = new Tab();
tab.setContent(content);
tab.setValue(value);
addTab(tab);
}
04.定义Tab的点击事件
//非active状态的Tab支持选中事件,改变tabList的内容
public void selectAt(int index) {
// 判断角标越界,当前的tab不可点击
if (!tabList.isEmpty() && tabList.size() > index && !tabList.get(index).isCurrent()) {
// 移除后面的tab数据
int size = tabList.size();
// 从尾部开始删除
for (int i = size - 1; i > index; i--) {
if (onTabListener != null) {
onTabListener.onRemoved(tabList.getLast());
}
tabList.removeLast();
}
// 修改当前选中的tab状态
tabList.getLast().setCurrent(true);
if (onTabListener != null) {
onTabListener.onActivated(tabList.getLast());
}
tabAdapter.notifyDataSetChanged();
}
}
public int getCurrentIndex() {
return tabList.size() - 1;
}
public void setOnTabListener(OnTabListener onTabListener) {
this.onTabListener = onTabListener;
}
public Tab getLastTab() {
if (!tabList.isEmpty()) {
return tabList.getLast();
}
return null;
}
05.设置itemView的Tab属性,存储列表中itemView的位置(是否最后一个)、内容等属性
public static class Tab {
private Context mContext;
/**
* 是否当前tab,非当前tab才可点击
*/
private boolean current = false;
/**
* 角标,从0开始
*/
private int index;
/**
* 面包屑内容
*/
private String content;
/**
* 业务携带的参数
*/
private Map<String, String> value;
public boolean isCurrent() {
return current;
}
public void setCurrent(boolean current) {
this.current = current;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Map<String, String> getValue() {
return value;
}
public void setValue(Map<String, String> value) {
this.value = value;
}
}
06.创建TabAdapter,设置itemView的内容和样式
//自定义列表的适配器,设置itemView的内容和样式
private class TabAdapter extends RecyclerView.Adapter<TabAdapter.ViewHolder> {
private Context mContext;
private List<Tab> list;
private OnClickListener onItemClickListener;
private float maxItemWidth;
public TabAdapter(Context mContext, List<Tab> list, OnClickListener onItemClickListener, float maxItemWidth) {
this.mContext = mContext;
this.list = list;
this.onItemClickListener = onItemClickListener;
this.maxItemWidth = maxItemWidth;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(mContext).inflate(R.layout.layout_breadcrumbs_tab, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
Tab item = list.get(position);
holder.tv.setText(item.getContent());
if (item.isCurrent()) {
holder.tv.setTextColor(Color.parseColor("#0B1D32"));
holder.iv.setVisibility(GONE);
holder.itemView.setOnClickListener(null);
} else {
holder.tv.setTextColor(Color.parseColor("#485666"));
holder.iv.setVisibility(VISIBLE);
//在onBindViewHolder()中设置点击事件,传递点击事件的回调(点击的位置)
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
v.setTag(position);
onItemClickListener.onClick(v);
}
}
});
}
if (maxItemWidth > 0) {
int width;
if (item.isCurrent()) {
width = (int) maxItemWidth;
} else {
width = (int) maxItemWidth - ScreenUtil.dip2px(mContext, 24);
}
holder.tv.setMaxWidth(width);
}
}
@Override
public int getItemCount() {
return list == null ? 0 : list.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private TextView tv;
private ImageView iv;
public ViewHolder(View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tv_content);
iv = itemView.findViewById(R.id.iv_arrow);
}
}
}
07.自定义Tab点击事件,也就是BreadCrumbsView的itemView的点击事件回调
public interface OnTabListener {
/**
* 新添加进来的回调
*
* @param tab
*/
void onAdded(Tab tab);
/**
* 再次显示到栈顶的回调
*
* @param tab
*/
void onActivated(Tab tab);
/**
* 被移除的回调
*
* @param tab
*/
void onRemoved(Tab tab);
}
2.使用自定义BreadCrumbView
01.设置BreadCrumbs的点击监听事件,获取点击的位置,自定义监听事件的回调方法
public class MainActivity extends AppCompatActivity {
private BreadCrumbsView breadCrumbsView;
LinkedList<Fragment> fragments = new LinkedList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
breadCrumbsView = findViewById(R.id.breadCrumbs);
breadCrumbsView.setOnTabListener(new BreadCrumbsView.OnTabListener() {
@Override
public void onAdded(BreadCrumbsView.Tab tab) {
Log.e("BreadCrumbsView", "BreadCrumbsView.OnTabListener#onAdded tab:" + tab.getIndex());
addFragment(tab);
}
@Override
public void onActivated(BreadCrumbsView.Tab tab) {
Log.e("BreadCrumbsView", "BreadCrumbsView.OnTabListener#onActivated tab:" + tab.getIndex());
}
@Override
public void onRemoved(BreadCrumbsView.Tab tab) {
Log.e("BreadCrumbsView", "BreadCrumbsView.OnTabListener#onRemoved tab:" + tab.getIndex());
removeLastFragment();
}
});
Button btnAdd = findViewById(R.id.btn_add);
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
StringBuilder sb = new StringBuilder("Tab");
sb.append(breadCrumbsView.getCurrentIndex()+1);
breadCrumbsView.addTab(sb.toString(), map);
}
});
}
02.创建底部fragment,实现页面的切换
private void addFragment(BreadCrumbsView.Tab tab) {
Fragment fragment = BlankFragment.newInstance(String.format("我是第%d个Fragment", tab.getIndex()), "" + tab.getIndex());
getSupportFragmentManager()
.beginTransaction()
.add(R.id.container, fragment, String.valueOf(tab.getIndex()))
.show(fragment)
.addToBackStack(null)
.commit();
fragments.add(fragment);
}
//触发点击事件,每次删除一个Tab,就会对应删除一个Fragment。从尾部开始删除,知道点击事件触发的位置
private void removeLastFragment() {
if (fragments != null && fragments.size() > 1) {
getSupportFragmentManager().popBackStackImmediate();
fragments.removeLast();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.show(fragments.getLast())
.commit();
fragmentManager.executePendingTransactions();
}
}
03.Activity后退按钮的点击事件,直接触发末位Tab点击事件
@Override
public void onBackPressed() {
if (fragments != null && fragments.size() > 1) {
breadCrumbsView.selectAt(fragments.size() - 1 - 1);
} else {
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("确认退出么?")
.setCancelable(true)
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).create();
dialog.show();
}
}
3.使用效果
4.项目目录
二、树型结构布局(TreeListView)
1.创建自定义适配器TreeAdapter
01.创建自定义列表的每一条itemView的数据集合:
public class TreePoint {
private String ID; //账号id
private String NNAME; //节点内容
private String PARENTID; //0表示父节点
private String ISLEAF; //1为叶子节点
private int DISPLAY_ORDER; // 1 //同一个级别的显示顺序
private boolean isExpand = false; //是否展开了
private boolean isSelected = false; //是否选中了
...省略了get/set方法
02.自定义TreeAdapter的构造函数:
public class TreeAdapter extends BaseAdapter {
private Context mcontext;
private List<TreePoint> pointList;
private String keyword = ""; //搜索输入的keyword
private HashMap<String, TreePoint> pointMap = new HashMap<>();
//两种操作模式:1表示点击 或者 2表示选择
private int operateMode = 1;
//设置列表item的样式
class ViewHolder {
TextView text;
ImageView icon;
ImageButton ib_select;
}
public TreeAdapter(final Context mcontext, List<TreePoint> pointList, HashMap<String, TreePoint> pointMap) {
this.mcontext = mcontext; //上下文
this.pointList = pointList; //列表数据
this.pointMap = pointMap; //TreePoint和PARENTID的映射
}
03.设置搜索关键字时列表的样式
/**
* 搜索的时候,先关闭所有的条目,然后,按照条件,找到含有关键字的数据
* 如果是叶子节点,
*/
public void setKeyword(String keyword) {
this.keyword = keyword;
for (TreePoint treePoint : pointList) {
treePoint.setExpand(false);
}
if (!keyword.isEmpty()) {
for (TreePoint tempTreePoint : pointList) {
if (tempTreePoint.getNNAME().contains(keyword)) {
tempTreePoint.setExpand(true);
//展开从最顶层到该点的所有节点
openExpand(tempTreePoint);
}
}
}
this.notifyDataSetChanged();
}
/**
* 从TreePoint开始一直展开到顶部
* @param treePoint
*/
private void openExpand(TreePoint treePoint) {
if ("0".equals(treePoint.getPARENTID())) {
treePoint.setExpand(true);
} else {
pointMap.get(treePoint.getPARENTID()).setExpand(true);
openExpand(pointMap.get(treePoint.getPARENTID()));
}
}
04.计算Item的数量,这里直接返回pointList.size()会出现列表数据错误
//准确计算数量
@Override
public int getCount() {
int count = 0;
for (TreePoint tempPoint : pointList) {
if ("0".equals(tempPoint.getPARENTID())) {
count++;
} else {
if (getItemIsExpand(tempPoint.getPARENTID())) {
count++;
}
}
}
return count;
}
//判断当前Id的tempPoint是否展开了
private boolean getItemIsExpand(String ID) {
for (TreePoint tempPoint : pointList) {
if (ID.equals(tempPoint.getID())) {
return tempPoint.isExpand();
}
}
return false;
}
05.获取列表position对应的item,此处需要对position进行转换,否则会出错
@Override
public Object getItem(int position) {
return pointList.get(convertPostion(position));
}
private int convertPostion(int position) {
int count = 0;
for (int i = 0; i < pointList.size(); i++) {
TreePoint treePoint = pointList.get(i);
if ("0".equals(treePoint.getPARENTID())) {
count++;
} else {
if (getItemIsExpand(treePoint.getPARENTID())) {
count++;
}
}
if (position == (count - 1)) {
return i;
}
}
return 0;
}
06.显示折叠布局,针对不同level的节点进行样式的设置
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(mcontext).inflate(R.layout.adapter_treeview, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
holder.ib_select = (ImageButton) convertView.findViewById(R.id.ib_select);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final TreePoint tempPoint = (TreePoint) getItem(position);
int level = TreeUtils.getLevel(tempPoint, pointMap);
holder.icon.setPadding(25 * level, holder.icon.getPaddingTop(), 0, holder.icon.getPaddingBottom());
if ("0".equals(tempPoint.getISLEAF())) { //如果为父节点
if (!tempPoint.isExpand()) { //未展开显示加号
holder.icon.setVisibility(View.VISIBLE);
holder.icon.setImageResource(R.drawable.outline_list_collapse);
} else { //展开了显示减号
holder.icon.setVisibility(View.VISIBLE);
holder.icon.setImageResource(R.drawable.outline_list_expand);
}
} else { //如果叶子节点,没有前面的加号
holder.icon.setVisibility(View.INVISIBLE);
}
if (operateMode == 1) {
holder.ib_select.setVisibility(View.VISIBLE);
holder.ib_select.setSelected(tempPoint.isSelected());
holder.ib_select.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onModeSelect(tempPoint);
//打印TreePoint的D
Toast.makeText(mcontext, "我点击的pointList的下标是:" + pointList.indexOf(tempPoint), Toast.LENGTH_SHORT).show();
//打印TreePoint的D
Toast.makeText(mcontext, "我点击的列表的位置是:" + position, Toast.LENGTH_SHORT).show();
}
});
} else {
holder.ib_select.setVisibility(View.GONE);
}
//如果存在搜索关键字
if (keyword != null && !"".equals(keyword) && tempPoint.getNNAME().contains(keyword)) {
int index = tempPoint.getNNAME().indexOf(keyword);
int len = keyword.length();
Spanned temp = Html.fromHtml(tempPoint.getNNAME().substring(0, index)
+ "<font color=#FF0000>" //字体变成红颜色
+ tempPoint.getNNAME().substring(index, index + len) + "</font>"
+ tempPoint.getNNAME().substring(index + len, tempPoint.getNNAME().length()));
holder.text.setText(temp);
} else {
holder.text.setText(tempPoint.getNNAME());
}
holder.text.setCompoundDrawablePadding(DensityUtil.dip2px(mcontext, 10));
return convertView;
}
07.item的点击事件
public void onItemClick(int position) {
TreePoint treePoint = (TreePoint) getItem(position);
if ("1".equals(treePoint.getISLEAF())) { //点击叶子节点
//处理回填
Toast.makeText(mcontext, getSubmitResult(treePoint), Toast.LENGTH_SHORT).show();
//打印TreePoint的ID
Toast.makeText(mcontext, "我自己的ID是:" + treePoint.getID(), Toast.LENGTH_SHORT).show();
//打印TreePoint的ID
Toast.makeText(mcontext, "我的ParentID是:" + treePoint.getPARENTID(), Toast.LENGTH_SHORT).show();
//打印TreePoint的D
Toast.makeText(mcontext, "我点击的列表的位置是:" + position, Toast.LENGTH_SHORT).show();
} else { //如果点击的是父类
if (treePoint.isExpand()) {
for (TreePoint tempPoint : pointList) {
if (tempPoint.getPARENTID().equals(treePoint.getID())) {
if ("0".equals(treePoint.getISLEAF())) {
tempPoint.setExpand(false);
}
}
}
treePoint.setExpand(false);
} else {
treePoint.setExpand(true);
}
}
this.notifyDataSetChanged();
}
08.节点的选择按钮点击事件
//选择操作
private void onModeSelect(TreePoint treePoint) {
if ("1".equals(treePoint.getISLEAF())) { //如果点击的是叶子节点的选择按钮
//点击回调,treePoint默认是未选中,点击事件之后treePoint是选中了
treePoint.setSelected(!treePoint.isSelected());
} else { //如果点击的是父节点的选中按钮,需要把下级的子节点全部选中
int position = pointList.indexOf(treePoint);
boolean isSelect = treePoint.isSelected();
treePoint.setSelected(!isSelect);
// if(position == -1){
// return ;
// }
// if(position == pointList.size()-1){
// return;
// }
position++;
for (; position < pointList.size(); position++) {
TreePoint tempPoint = pointList.get(position);
if (tempPoint.getPARENTID().equals(treePoint.getPARENTID())) { //如果找到和自己同级的数据就返回,和自己同级的数据的上面的数据都是自己的下级
break;
}
tempPoint.setSelected(!isSelect);
}
}
this.notifyDataSetChanged();
}
//选中所有的treePoint
public void selectAllPoint() {
for (TreePoint tempPoint : pointList) {
if (!tempPoint.isSelected()) {
tempPoint.setSelected(true);
this.notifyDataSetChanged();
}
}
}
//取消选中所有的treePoint
public void unSelectAllPoint() {
for (TreePoint tempPoint : pointList) {
tempPoint.setSelected(false);
this.notifyDataSetChanged();
}
}
09.显示节点的路径
private String getSubmitResult(TreePoint treePoint) {
StringBuilder sb = new StringBuilder();
addResult(treePoint, sb);
String result = sb.toString();
if (result.endsWith("-")) {
result = result.substring(0, result.length() - 1);
}
return result;
}
private void addResult(TreePoint treePoint, StringBuilder sb) {
if (treePoint != null && sb != null) {
sb.insert(0, treePoint.getNNAME() + "-");
if (!"0".equals(treePoint.getPARENTID())) {
addResult(pointMap.get(treePoint.getPARENTID()), sb);
}
}
}
2.使用自定义TreeAdapter
01.为树型布局赋值
//初始化数据
//数据特点:TreePoint 之间的关系特点 id是任意唯一的。 如果为根节点 PARENTID 为"0" 如果没有子节点,也就是本身是叶子节点的时候ISLEAF = "1"
// DISPLAY_ORDER 是同一级中 显示的顺序
//如果需要做选中 单选或者多选,只需要给TreePoint增加一个选中的属性,在ReasonAdapter中处理就好了
private void initData() {
pointList.clear();
int id =1000;
int parentId = 0;
int parentId2 = 0;
int parentId3 = 0;
for(int i=1;i<3;i++){
id++;
pointList.add(new TreePoint(""+id,"分类"+i,"" + parentId,"0",i));
for(int j=1;j<3;j++){
if(j==1){
parentId2 = id;
}
id++;
pointList.add(new TreePoint(""+id,"分类"+i+"_"+j,""+parentId2,"0",j));
for(int k=1;k<3;k++){
if(k==1){
parentId3 = id;
}
id++;
pointList.add(new TreePoint(""+id,"分类"+i+"_"+j+"_"+k,""+parentId3,"1",k));
}
}
}
//打乱集合中的数据
Collections.shuffle(pointList);
//对集合中的数据重新排序
updateData();
}
02.对赋值的数据进行排序
//对数据排序 深度优先
private void updateData() {
for (TreePoint treePoint : pointList) {
//pointMap是treePoint的ID和treePoint的映射
pointMap.put(treePoint.getID(), treePoint);
}
Collections.sort(pointList, new Comparator<TreePoint>() {
@Override
public int compare(TreePoint lhs, TreePoint rhs) {
int llevel = TreeUtils.getLevel(lhs, pointMap);
int rlevel = TreeUtils.getLevel(rhs, pointMap);
if (llevel == rlevel) {
if (lhs.getPARENTID().equals(rhs.getPARENTID())) { //左边小
return lhs.getDISPLAY_ORDER() > rhs.getDISPLAY_ORDER() ? 1 : -1;
} else { //如果父辈id不相等
//同一级别,不同父辈
TreePoint ltreePoint = TreeUtils.getTreePoint(lhs.getPARENTID(), pointMap);
TreePoint rtreePoint = TreeUtils.getTreePoint(rhs.getPARENTID(), pointMap);
return compare(ltreePoint, rtreePoint); //父辈
}
} else { //不同级别
if (llevel > rlevel) { //左边级别大 左边小
if (lhs.getPARENTID().equals(rhs.getID())) {
return 1;
} else {
TreePoint lreasonTreePoint = TreeUtils.getTreePoint(lhs.getPARENTID(), pointMap);
return compare(lreasonTreePoint, rhs);
}
} else { //右边级别大 右边小
if (rhs.getPARENTID().equals(lhs.getID())) {
return -1;
}
TreePoint rreasonTreePoint = TreeUtils.getTreePoint(rhs.getPARENTID(), pointMap);
return compare(lhs, rreasonTreePoint);
}
}
}
});
adapter.notifyDataSetChanged();
}
二、分页布局(jetpack的paging库)
1.定义适配器、数据接口Service、数据源
01.首先当然是导入依赖
def paging_version = "3.0.0-alpha12"
implementation "androidx.paging:paging-runtime:$paging_version"
02.定义适配器:
class MyAdapter(private val context: Context) : PagingDataAdapter<User, MyAdapter.ViewHolder>(Comparator) {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvName: TextView? = null
init {
tvName = itemView.findViewById(R.id.tvName)
}
fun bind(item: User?) {
tvName?.text = item?.name.toString()
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): UserViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.adapter_user_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
// Note that item may be null. ViewHolder must support binding a
// null item as a placeholder.
holder.bind(item)
holder.itemView.setOnClickListener {
Toast.makeText(context, "haha", Toast.LENGTH_SHORT).show()
}
}
object Comparator : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
// Id is unique.
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
}
03.定义API接口请求数据,模拟耗时操作
class ExampleBackendService {
private var count = 0
suspend fun searchUsers(): Response {
Log.d("duo_shine", "发起请求 模拟耗时操作")
return withContext(Dispatchers.IO) {
//模拟耗时请求的操作
delay(2000)
val data = ArrayList<User>()
for(i in count until count + 20){
data.add(User(i, i))
}
count +=20
Response(data, count)
}
}
}
04.定义数据源:
class ExamplePagingSource(private val backend: ExampleBackendService) : PagingSource<Int, User>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
return try {
//从后台或数据库异步获取数据
val response = backend.searchUsers()
val nextKey = response.nextKey
//组成成功
LoadResult.Page(
data = response.response,
prevKey = null,
nextKey = if(nextKey!! >= 100) null else nextKey
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
2.使用Paging分页布局
01.设置自定义适配器,实现分页请求数据的效果
class MainActivity : AppCompatActivity() {
private val flow = Pager(PagingConfig(pageSize = 1)) {
ExamplePagingSource(ExampleBackendService())
}.flow
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = UserAdapter(this)
recyclerView.adapter = adapter
lifecycleScope.launch {
flow.collectLatest {
adapter.submitData(it) //使用submitData()方法直接提交数据到适配器中
}
}
}
}
三、自定义底部导航栏(BottomNavigationView)
创建自定义工具类NavHelper
01.NavHelper用于解决页面中Fragment的调度与重用问题
public class NavHelper<T> {
// 所有的Tab集合
private final SparseArray<Tab<T>> tabs = new SparseArray<>();
// 用于初始化的必须参数
private final Context context;
private final int containerId;
private final FragmentManager fragmentManager;
private final OnTabChangedListener<T> listener;
// 当前的一个选中的Tab
private Tab<T> currentTab;
public NavHelper(Context context, int containerId,
FragmentManager fragmentManager,
OnTabChangedListener<T> listener) {
this.context = context;
this.containerId = containerId;
this.fragmentManager = fragmentManager;
this.listener = listener;
}
02.NavHelper中处理BottomNavigationView的点击事件:
/**
* 添加Tab
*
* @param menuId Tab对应的菜单Id
* @param tab Tab
*/
public NavHelper<T> add(int menuId, Tab<T> tab) {
tabs.put(menuId, tab);
return this;
}
/**
* 获取当前的显示的Tab
*
* @return 当前的Tab
*/
public Tab<T> getCurrentTab() {
return currentTab;
}
/**
* 执行点击菜单的操作
*
* @param menuId 菜单的Id
* @return 是否能够处理这个点击
*/
public boolean performClickMenu(int menuId) {
// 集合中寻找点击的菜单对应的Tab,
// 如果有则进行处理
Tab<T> tab = tabs.get(menuId);
if (tab != null) {
doSelect(tab);
return true;
}
//默认返回的false,处理成功返回的才是true
return false;
}
/**
* 进行Tab选择操作
*
* @param tab Tab
*/
private void doSelect(Tab<T> tab) {
//首先将之前选中的tab置为空
Tab<T> oldTab = null;
if (currentTab != null) {
oldTab = currentTab;
if (oldTab == tab) {
// 如果说当前的Tab就是点击的Tab,那么我们不做处理
return;
}
}
// 赋值并调用切换方法
currentTab = tab;
doTabChanged(currentTab, oldTab);
}
/**
* 进行Fragment的调度操作
*
* @param newTab 新的
* @param oldTab 旧的
*/
private void doTabChanged(Tab<T> newTab, Tab<T> oldTab) {
FragmentTransaction ft = fragmentManager.beginTransaction();
if (oldTab != null) {
if (oldTab.fragment != null) {
// 从界面移除,但是还在Fragment的缓存空间中
ft.detach(oldTab.fragment);
}
}
if (newTab != null) {
if (newTab.fragment == null) {
// 首次新建,直接从Tab的clx参数中将fragment创建出来
Fragment fragment = fragmentManager.getFragmentFactory().instantiate(context.getClassLoader(), newTab.clx.getName());
// 缓存起来
newTab.fragment = fragment;
// 提交到FragmentManger
ft.add(containerId, fragment, newTab.clx.getName());
} else {
// 从FragmentManger的缓存空间中重新加载到界面中
ft.attach(newTab.fragment);
}
}
// 提交事务
ft.commit();
// 通知回调
notifyTabSelect(newTab, oldTab);
}
/**
* 回调我们的监听器
*
* @param newTab 新的Tab<T>
* @param oldTab 旧的Tab<T>
*/
private void notifyTabSelect(Tab<T> newTab, Tab<T> oldTab) {
if (listener != null) {
listener.onTabChanged(newTab, oldTab);
}
}
03.NavHelper中定义Tab这个封装类:
/**
* 所有的Tab基础属性,
* arg1:fragment的class信息,用于后面利用context.getClassLoader()获取这个fragment
* arg2:extra则是fragment对应的额外信息,用于回调到MainActivity之中
* @param <T> 范型的额外参数
*/
public static class Tab<T> {
public Tab(Class<?> clx, T extra) {
this.clx = clx;
this.extra = extra;
}
// Fragment对应的Class信息
public Class<?> clx;
// 额外的字段,用户自己设定需要使用
public T extra;
// 内部缓存的对应的Fragment,
// Package权限,外部无法使用
Fragment fragment;
}
/**
* 定义事件处理完成后的回调接口
*/
public interface OnTabChangedListener<T> {
void onTabChanged(Tab<T> newTab, Tab<T> oldTab);
}
使用自定义适配器
01.在BottomNavigationView的点击监听事件中将事件流转接到工具类中
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNavigation = findViewById(R.id.navigation);
mTitle = findViewById(R.id.txt_title);
mAction = findViewById(R.id.btn_action);
// 初始化底部辅助工具类
mNavHelper = new NavHelper<>(this, R.id.lay_container, getSupportFragmentManager(), this);
mNavHelper.add(R.id.action_home, new NavHelper.Tab<>(ActiveFragment.class, R.string.title_home))
.add(R.id.action_group, new NavHelper.Tab<>(GroupFragment.class, R.string.title_group))
.add(R.id.action_contact, new NavHelper.Tab<>(ContactFragment.class, R.string.title_contact));
// 添加对底部按钮点击的监听
mNavigation.setOnNavigationItemSelectedListener(this);
// 从底部导中接管我们的Menu,然后进行手动的触发第一次点击
Menu menu = mNavigation.getMenu();
// 触发首次选中Home
menu.performIdentifierAction(R.id.action_home, 0);
}
/**
* 当我们的底部导航被点击的时候触发
*
* @param item MenuItem
* @return True 代表我们能够处理这个点击
*/
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
// 转接事件流到工具类中
return mNavHelper.performClickMenu(item.getItemId());
}
02.在MainActivity中处理工具类回调给我们的监听器
/**
* 回调我们的监听器,可以进行页面逻辑的切换操作
*
* @param newTab 新的Tab<T>
* @param oldTab 旧的Tab<T>
*/
private void notifyTabSelect(Tab<T> newTab, Tab<T> oldTab) {
if (listener != null) {
listener.onTabChanged(newTab, oldTab);
}
}
/**
* 所有的Tab基础属性
*
* @param <T> 范型的额外参数
*/
public static class Tab<T> {
public Tab(Class<?> clx, T extra) {
this.clx = clx;
this.extra = extra;
}
// Fragment对应的Class信息
public Class<?> clx;
// 额外的字段,用户自己设定需要使用
public T extra;
// 内部缓存的对应的Fragment,
// Package权限,外部无法使用
Fragment fragment;
}
/**
* 定义事件处理完成后的回调接口
*/
public interface OnTabChangedListener<T> {
void onTabChanged(Tab<T> newTab, Tab<T> oldTab);
}
四、自定义RecyclerView(单选、多选、长按删除)
01.列表样式的核心就是适配器
01.定义适配器的构造函数和初始化列表数据的选中状态:
//自定义适配器,继承RecyclerView.Adapter
public class RecyViewAdapter extends RecyclerView.Adapter<RecyViewAdapter.ViewHolder> {
private Context mContext;
private List<String>list;
private HashMap<Integer, Boolean> maps = new HashMap<Integer, Boolean>();//保存列表中每个item的选中状态
public RecyclerViewOnItemClickListener onItemClickListener;
public RecyViewAdapter(Context mContext, List<String> list) {
this.mContext = mContext;
this.list = list;
initMap();
}
//设置每次进入列表,item都是未选中的状态
private void initMap() {
for (int i = 0; i <list.size() ; i++) {
maps.put(i,false);
}
}
02.覆写父类的三个重要方法:
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_provice_chose, viewGroup, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, @SuppressLint("RecyclerView") final int i) {
viewHolder.province_name.setText(list.get(i));
viewHolder.cbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
maps.put(i, isChecked);
}
});
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
maps.put(i, true);
if (onItemClickListener != null) {
onItemClickListener.onItemLongClick(view, i);
}
}
});
if (maps.get(i) == null) {
maps.put(i, false);
}
//没有设置tag之前会有item重复选框出现,设置tag之后,此问题解决
viewHolder.cbox.setChecked(maps.get(i));
}
@Override
public int getItemCount() {
return list.size();
}
03.创建自定义方法,全选、反选、多选和删除:
//获取最终的map存储数据
public Map<Integer, Boolean> getMap() {
return maps;
}
//全选方法
public void All() {
Set<Map.Entry<Integer, Boolean>> entries = maps.entrySet();
boolean shouldall = false;
for (Map.Entry<Integer, Boolean> entry : entries) {
Boolean value = entry.getValue();
if (!value) {
shouldall = true;
break;
}
}
for (Map.Entry<Integer, Boolean> entry : entries) {
entry.setValue(shouldall);
}
notifyDataSetChanged();
}
//反选
public void neverall() {
Set<Map.Entry<Integer, Boolean>> entries = maps.entrySet();
for (Map.Entry<Integer, Boolean> entry : entries) {
entry.setValue(!entry.getValue());
}
notifyDataSetChanged();
}
//多选
public void MultiSelection(int position) {
//对当前状态取反
if (maps.get(position)) {
maps.put(position, false);
} else {
maps.put(position, true);
}
notifyItemChanged(position);
}
//删除数据
public void delete(int position) {
list.remove(position);
notifyItemRemoved(position);
//此处是重点,解决了删除数据后列表错位的问题
notifyItemRangeChanged(position, list.size() - 1);
}
04.设置自定义ViewHolder以及监听事件的回调:
public class ViewHolder extends RecyclerView.ViewHolder{
private RecyclerViewOnItemClickListener mListener;//接口
private CheckBox cbox;
private TextView province_name;
public ViewHolder(View itemView) {
super(itemView);
cbox = itemView.findViewById(R.id.cbox);
province_name = itemView.findViewById(R.id.province_name);
}
}
//回调的接口
public void setItemClickListener(RecyclerViewOnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
//接口回调设置点击事件
public interface RecyclerViewOnItemClickListener {
//点击事件
void onItemLongClick(View view, int position);
}
02.使用自定义适配器:
01.给列表传递数据
private void initData() {
list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add("测试数据----" + i);
}
adapter = new RecyViewAdapter(MainActivity.this, list);
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(MainActivity.this, 3);
recyView.setLayoutManager(layoutManager);
recyView.setAdapter(adapter);
adapter.notifyDataSetChanged();
adapter.setItemClickListener(new RecyViewAdapter.RecyclerViewOnItemClickListener() {
@Override
public void onItemLongClick(View view, int position) {
//多次点击删除
if (prelongTim == 0) {
//第一次单击时间
prelongTim = System.currentTimeMillis();
Toast.makeText(MainActivity.this, "第一次点击的时间" + prelongTim, Toast.LENGTH_SHORT).show();
} else {
curTime = System.currentTimeMillis();//本地单击的时间
if ((curTime - prelongTim) < 500) {
//连续点击删除数据
Toast.makeText(MainActivity.this, "点太快了,死鬼", Toast.LENGTH_SHORT).show();
adapter.delete(position);
}
prelongTim = 0; //重新开始统计点击时间差
}
}
});
recyView.setLongClickable(true);
recyView.addItemDecoration(new GridSpacingItemDecoration(3, 80, true));
}
02.设置页面上按钮的监听事件:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_over:
if (!isSelect) {
isSelect = true;//全选
adapter.All();
tv_over.setText("取消全选");
} else {
isSelect = false;//取消全选
adapter.neverall();
tv_over.setText("全选");
}
break;
case R.id.commit:
String content = "";
listdatas.clear();
Map<Integer, Boolean> map = adapter.getMap();
for (int i = 0; i < list.size(); i++) {
if (map.get(i)) {
listdatas.add(list.get(i));
}
}
//将选中的数据显示出来,多个数据的话使用","分割
for (int j = 0; j < listdatas.size(); j++) {
content += listdatas.get(j) + ",";
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if (content.length() == 0) {
builder.setMessage("请选择数据");
} else {
builder.setMessage(content.substring(0, content.length() - 1));
}
builder.create().show();
break;
case R.id.delete:
Map<Integer, Boolean> tMap = adapter.getMap();
for (int i = 0; i < list.size(); i++) {
if (tMap.get(i)) {
adapter.delete(i);
}
}
break;
default:
break;
}
}
五、自定义RecyclerView(item的拖拽监听)
01.创建自定义适配器:
01.创建自定义监听器
public interface ItemTouchListener {
//数据交换
void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target);
//数据删除
void onItemDissmiss(RecyclerView.ViewHolder source);
//drag或者swipe选中
void onItemSelect(RecyclerView.ViewHolder source);
//状态清除
void onItemClear(RecyclerView.ViewHolder source);
}
02.创建自定义回调,处理RecycleView的选中,拖拽移动,拖拽删除的实现类
/**
*
* Description: 处理RecycleView的选中,拖拽移动,拖拽删除的实现类
*/
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final ItemTouchListener touchListener;
/**
*
* @param listener
*/
public SimpleItemTouchHelperCallback(ItemTouchListener listener) {
touchListener = listener;
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
//int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //允许上下的拖动
//int dragFlags =ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; //允许左右的拖动
//int swipeFlags = ItemTouchHelper.LEFT; //只允许从右向左侧滑
//int swipeFlags = ItemTouchHelper.DOWN; //只允许从上向下侧滑
//一般使用makeMovementFlags(int,int)或makeFlag(int, int)来构造我们的返回值
//makeMovementFlags(dragFlags, swipeFlags)
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; //允许上下左右的拖动
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
//通过接口传递拖拽交换数据的起始位置和目标位置的ViewHolder
touchListener.onItemMove(viewHolder, target);
return true;
}
@Override
public boolean isLongPressDragEnabled() {
return true;//长按启用拖拽
}
@Override
public boolean isItemViewSwipeEnabled() {
return false; //不启用拖拽删除
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
//移动删除回调,如果不用可以不用理
// mAdapter.onItemDissmiss(viewHolder);
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
//当滑动或者拖拽view的时候通过接口返回该ViewHolder
touchListener.onItemSelect(viewHolder);
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (!recyclerView.isComputingLayout()) {
//当需要清除之前在onSelectedChanged或者onChildDraw,onChildDrawOver设置的状态或者动画时通过接口返回该ViewHolder
touchListener.onItemClear(viewHolder);
}
}
}
03.自定义适配器继承自定义监听器
/**
*
* Description: 拖拽的recyclerView 的 adapter
*/
public class DragRecyclerViewAdapter extends RecyclerView.Adapter<DragRecyclerViewAdapter.DragHolder> implements ItemTouchListener {
private Context context;
private List<String> contentList = new ArrayList<>();
private boolean isShowDelete = false;//是否显示删除图标
public DragRecyclerViewAdapter(Context context, List<String> contentList) {
this.context = context;
this.contentList = contentList;
}
@NonNull
@Override
public DragHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_drag_recyclerview, parent, false);
return new DragHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final DragHolder holder, int position) {
if (isShowDelete){
holder.img_delete.setVisibility(View.VISIBLE);
if (position == 0){
holder.img_delete.setVisibility(View.GONE);
}
}else {
holder.img_delete.setVisibility(View.GONE);
}
holder.tv_content.setText(contentList.get(position));
//删除
holder.img_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemDissmiss(holder);
}
});
}
@Override
public int getItemCount() {
return contentList.size();
}
@Override
public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
int fromPosition = source.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if (fromPosition == 0 || toPosition == 0){//这个判断根据实际修改,可加可不加
Toast.makeText(context,"第一个不可移动",Toast.LENGTH_SHORT).show();
}else {
if (fromPosition < contentList.size() && toPosition < contentList.size()) {
//交换数据位置,交换逻辑发生的地方
Collections.swap(contentList, fromPosition, toPosition);
//刷新位置交换
notifyItemMoved(fromPosition, toPosition);
}
//移动过程中移除view的放大效果
onItemClear(source);
}
}
@Override
public void onItemDissmiss(RecyclerView.ViewHolder source) {
int position = source.getAdapterPosition();
contentList.remove(position); //移除数据
notifyItemRemoved(position);//刷新数据移除
}
@Override
public void onItemSelect(RecyclerView.ViewHolder source) {
//当拖拽选中时放大选中的view
source.itemView.setScaleX(1.2f);
source.itemView.setScaleY(1.2f);
}
@Override
public void onItemClear(RecyclerView.ViewHolder source) {
//拖拽结束后恢复view的状态
source.itemView.setScaleX(1.0f);
source.itemView.setScaleY(1.0f);
}
public class DragHolder extends RecyclerView.ViewHolder {
private final TextView tv_content;
private final ImageView img_delete;//删除图标
public DragHolder(View itemView) {
super(itemView);
tv_content = itemView.findViewById(R.id.tv_content);
img_delete = itemView.findViewById(R.id.img_delete);
}
}
public void setContentList(Context context, List<String> contentList,boolean isShowDelete){
this.context = context;
this.contentList = contentList;
this.isShowDelete = isShowDelete;
notifyDataSetChanged();
}
}
02.使用自定义适配器:
注意:使用SimpleItemTouchHelperCallback(dragRecyclerViewAdapter)传入一个dragRecyclerViewAdapter(ItemTouchListener的子类)
public class MainActivity extends AppCompatActivity {
private ArrayList<String> contentList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setDragData();
RecyclerView dragRecyclerView = findViewById(R.id.dragRecyclerView);
//创建adapter
DragRecyclerViewAdapter dragRecyclerViewAdapter = new DragRecyclerViewAdapter(this, contentList);
//设置默认的布局方式
dragRecyclerView.setLayoutManager(new GridLayoutManager(this, 5,GridLayoutManager.VERTICAL, false));
//设置adapter
dragRecyclerView.setAdapter(dragRecyclerViewAdapter);
//创建SimpleItemTouchHelperCallback
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(dragRecyclerViewAdapter);
//用Callback构造ItemtouchHelper
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
//调用ItemTouchHelper的attachToRecyclerView方法建立联系
touchHelper.attachToRecyclerView(dragRecyclerView);
//编辑
TextView tv_edit = findViewById(R.id.tv_edit);
tv_edit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (tv_edit.getText().toString().equals("编辑")){
tv_edit.setText("完成");
dragRecyclerViewAdapter.setContentList(MainActivity.this, contentList,true);
}else {
tv_edit.setText("编辑");
dragRecyclerViewAdapter.setContentList(MainActivity.this, contentList,false);
}
}
});
}
private void setDragData(){
for (int i = 1; i < 21; i++){
contentList.add(i + "");
}
}
}
六、自定义RecyclerView下拉刷新
01.创建自定义适配器
01.继承RecyclerView.Adapter<RecyclerView.ViewHolder>(),定义FooterViewHolder
class NewsAdapter(val list: List<Data>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val Type_Footer = 0
private val Type_Item = 1
private var load_more_status: Int? = null
val PULLUP_LOAD_MORE: Int = 3
val LOADING_MORE: Int = 4
val LOADING_END: Int = 5
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var tvContent: TextView = view.findViewById(R.id.tvContent)
var tvPlay: TextView = view.findViewById(R.id.tvPlay)
}
inner class FooterViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var content: TextView = view.findViewById(R.id.content)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if(viewType == Type_Footer) {
val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_footer_item, parent, false)
return FooterViewHolder(view)
}
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.layout_joke_list_item, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is NewsAdapter.FooterViewHolder) {
when(load_more_status) {
PULLUP_LOAD_MORE -> {
holder.content.text = "上拉加载更多"
}
LOADING_MORE -> {
holder.content.text = "正在加载更多数据..."
}
LOADING_END -> {
holder.content.text = "没有更多数据了..."
}
}
} else if(holder is NewsAdapter.ViewHolder) {
holder.tvContent.text = list[position].content
holder.tvPlay.text = "播放"
}
}
override fun getItemCount(): Int = list.size + 1
override fun getItemViewType(position: Int): Int = if(position + 1 == itemCount) Type_Footer else Type_Item
fun changMoreStatus(status: Int) {
load_more_status = status
notifyDataSetChanged()
}
}
02.使用自定义适配器
class MainActivity : AppCompatActivity(), MainContract.View {
//页码
private var currentPage = 1
//数据源
private var mList = ArrayList<Data>()
private lateinit var mJokeAdapter: NewsAdapter
private lateinit var mJokeListView: RecyclerView
private lateinit var mPresenter: MainPresenter
private var totalItemCount: Int? = null
private var lastVisibleItemPosition: Int? = null
private var mLayoutManager: LinearLayoutManager? = null
private lateinit var context: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mJokeListView = findViewById(R.id.mJokeListView)
context = applicationContext
mPresenter = MainPresenter()
initList()
mPresenter.getData(0, 0)
mJokeListView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
totalItemCount = mLayoutManager?.itemCount
//已经刷新到底部了
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == (totalItemCount!! - 1)) {
currentPage += 1
mJokeAdapter.changMoreStatus(mJokeAdapter.LOADING_MORE)
Toast.makeText(context, "正在加载更多数据", Toast.LENGTH_SHORT).show()
mPresenter.getData(currentPage, 1)
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
lastVisibleItemPosition = mLayoutManager?.findLastVisibleItemPosition()
}
})
}
override fun onStart() {
super.onStart()
mPresenter.takeView(this)
}
override fun refreshView(orientation: Int, data: List<Data>) {
if (orientation == 0) {
//列表要清空
mList.clear()
mList.addAll(data)
Toast.makeText(this, "上拉加载更多数据", Toast.LENGTH_SHORT).show()
mJokeAdapter.changMoreStatus(mJokeAdapter.PULLUP_LOAD_MORE)
//适配器要全部刷新
mJokeAdapter.notifyDataSetChanged()
} else if (orientation == 1) {
//上一次的最大值
val positionStart = mList.size
mList.addAll(data)
Log.d("hy55", "$data")
Toast.makeText(this, "上拉加载更多数据", Toast.LENGTH_SHORT).show()
mJokeAdapter.changMoreStatus(mJokeAdapter.PULLUP_LOAD_MORE)
//局部刷新
mJokeAdapter.notifyItemRangeInserted(positionStart, data.size)
}
}
override fun loadFinish() {
Toast.makeText(this, "没有数据了", Toast.LENGTH_SHORT).show()
mJokeAdapter.changMoreStatus(mJokeAdapter.LOADING_END)
}
//初始化列表
private fun initList() {
mLayoutManager = LinearLayoutManager(this)
mJokeListView.layoutManager = mLayoutManager
mJokeListView.layoutManager = LinearLayoutManager(this)
mJokeAdapter = NewsAdapter(mList)
//设置适配器
mJokeListView.adapter = mJokeAdapter
}
}
03.定义数据源
class MainPresenter : MainContract.Presenter {
private var view: MainContract.View? = null
override fun takeView(view: MainContract.View) {
this.view = view
}
override fun dropView(view: MainContract.View) {
this.view = null
}
fun getData(currentPage: Int, orientation: Int) {
HttpManager.queryJokeList(currentPage, object : Callback<JokeListData> {
override fun onFailure(call: Call<JokeListData>, t: Throwable) {
L.i("onFailure$t")
}
override fun onResponse(call: Call<JokeListData>, response: Response<JokeListData>) {
L.i("${response.body()?.error_code}")
val data = listOf(
Data("123", "456", 1, "789"),
Data("123", "456", 1, "789"),
Data("123", "456", 1, "789")
)
//接口无法加载数据,测试数据是否能够正常加载
if (orientation == 0) {
view?.refreshView(orientation, data)
} else {
if (response.body()?.error_code == 10012) {
view?.loadFinish()
} else {
//追加在尾部
response.body()?.result?.data?.let {
view?.refreshView(orientation, it)
}
}
}
}
})
}
}
04.效果图
01.初始加载数据:
02.上拉刷新加载数据:
七、ListAdapter的使用
01.创建自定义适配器
01.自定义适配器继承ListAdapter
//构建ListView的Adapter,不需要将数据带进去
class NewsAdapter :
ListAdapter<Data, NewsAdapter.ViewHolder>(Diff()) {
//构建ListView的数据比较结果
class Diff : DiffUtil.ItemCallback<Data>() {
override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.hashId == newItem.hashId
}
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvContent: TextView = view.findViewById(R.id.tvContent)
var tvPlay: TextView = view.findViewById(R.id.tvPlay)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_joke_list_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvContent.text = getItem(position).content
holder.tvPlay.text = "播放"
}
}
02.使用自定义适配器
class MainActivity : AppCompatActivity(), OnRefreshListener, OnRefreshLoadMoreListener,
MainContract.View {
//页码
private var currentPage = 1
//数据源
private var mList = ArrayList<Data>()
private lateinit var mJokeAdapter: NewsAdapter
private var refreshLayout: SmartRefreshLayout? = null
private var mJokeListView: RecyclerView? = null
private lateinit var mPresenter: MainPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
refreshLayout = findViewById(R.id.refreshLayout)
mJokeListView = findViewById(R.id.mJokeListView)
mJokeAdapter = NewsAdapter()
mPresenter = MainPresenter()
initList()
mPresenter.getData(0, 0)
}
override fun onStart() {
super.onStart()
mPresenter.takeView(this)
}
override fun refreshView(orientation: Int, data: List<Data>) {
if (orientation == 0) {
refreshLayout?.finishRefresh()
//列表要清空
mList.clear()
mList.addAll(data)
mJokeAdapter.submitList(mList)
//适配器要全部刷新
mJokeAdapter.notifyDataSetChanged()
} else if (orientation == 1) {
refreshLayout?.finishLoadMore()
//上一次的最大值
val positionStart = mList.size
mList.addAll(data)
//需要注意:submitList()方法不能传参数MutableList
mJokeAdapter.submitList(mList)
//局部刷新
mJokeAdapter.notifyItemRangeInserted(positionStart, data.size)
}
}
//初始化列表
private fun initList() {
//刷新组件
refreshLayout?.setRefreshHeader(ClassicsHeader(this))
refreshLayout?.setRefreshFooter(ClassicsFooter(this))
//监听
refreshLayout?.setOnRefreshListener(this)
refreshLayout?.setOnRefreshLoadMoreListener(this)
mJokeListView?.layoutManager = LinearLayoutManager(this)
//设置适配器
mJokeListView?.adapter = mJokeAdapter
}
override fun onRefresh(refreshLayout: RefreshLayout) {
currentPage = 1
mPresenter.getData(currentPage, 0)
}
override fun onLoadMore(refreshLayout: RefreshLayout) {
currentPage += 1
mPresenter.getData(currentPage, 1)
}
}
八、自定义提示框
01.布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/layout1"
android:gravity="center_horizontal"
>
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示带取消、中立和确定按钮的对话框"/>
<Button android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示列表的对话框"/>
<Button android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示带单选列表对话框"/>
<Button android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示带多选列表对话框"/>
</LinearLayout>
02.创建并使用自定义提示框(四种样式的提示框)
public class MainActivity extends AppCompatActivity {
private boolean[] checkedItems;//记录各个列表项的状态
private String[] items;//各列表项要显示的内容
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//显示带取消、中立和确定按钮的对话框
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
AlertDialog alert = new AlertDialog.Builder(MainActivity.this).create();
alert.setIcon(R.mipmap.ic_launcher);//设置对话框的图标
alert.setTitle("系统提示");//设置对话框的标题
alert.setMessage("显示带取消、中立和确定按钮的对话框!");//设置对话框显示的内容
//添加“取消”按钮
alert.setButton(DialogInterface.BUTTON_NEGATIVE, "取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "您单击了取消按钮", Toast.LENGTH_SHORT).show();
}
});
//添加“确定”按钮
alert.setButton(DialogInterface.BUTTON_POSITIVE, "确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "您单击了确定按钮", Toast.LENGTH_SHORT).show();
}
});
//添加“中立”按钮
alert.setButton(DialogInterface.BUTTON_NEUTRAL, "中立",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "您单击了中立按钮", Toast.LENGTH_SHORT).show();
}
});
alert.show();//显示对话框
}
});
//显示列表的对话框
Button button2 = findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
final String[] items = new String[]{"唱歌", "跳舞", "美术", "远足旅行", "摄影"};
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(R.mipmap.ic_launcher);
builder.setTitle("请选择你的爱好:");
//添加列表项
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "您选择了" + items[which], Toast.LENGTH_SHORT).show();
}
});
builder.create().show();//创建对话框并显示
}
});
//显示带单选列表对话框
Button button3 = (Button) findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
final String[] items = new String[]{"标准", "无声", "会议", "户外", "离线"};
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(R.mipmap.ic_launcher);
builder.setTitle("请选择要使用的情景模式:");
builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "您选择了" + items[which], Toast.LENGTH_SHORT).show();
}
});
builder.setPositiveButton("确定", null);
builder.create().show();//创建对话框并显示
}
});
//显示带多选列表对话框
Button button4 = (Button) findViewById(R.id.button4);
button4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
checkedItems = new boolean[]{false, true, false, true, false};//记录各列表的状态
//各列表项要显示的内容
items = new String[]{"植物大战僵尸", "愤怒的小鸟", "泡泡龙", "开心消消乐", "地铁跑酷"};
//显示带单选列表框的对话框
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(R.mipmap.ic_launcher);
builder.setTitle("请选择您喜欢的游戏:");
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
checkedItems[which] = isChecked;
}
});
//为对话框添加"确定"按钮
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String result = "";
for (int i = 0; i < checkedItems.length; i++) {
if (checkedItems[i]) {
result += items[i] + "、";
}
}
//当result不为空时,通过消息提示框显示选择的结果
if (!"".equals(result)) {
result = result.substring(0, result.length() - 1);//去掉最后的"、"号
Toast.makeText(MainActivity.this, "您选择了:[" + result + "]", Toast.LENGTH_SHORT).show();
}
}
});
builder.create().show();//创建对话框并显示
}
});
}
}
九、自定义列表对话框
01.创建列表的适配器,设置监听事件(监听事件在适配器中处理)
public class DialogListAdapter extends BaseAdapter {
private List<HashMap<String,String>> mapList;
private LayoutInflater inflater;
private Context mContext;
public DialogListAdapter(Context context, List<HashMap<String,String>>list){
this.mContext = context;
this.mapList = list;
inflater = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
return mapList.size(); //数据源的长度
}
@Override
public Object getItem(int position) {
return mapList.get(position); //返回数据源的其中某一个对象
}
@Override
public long getItemId(int position) {
return position; //返回adapter中的其中一个项的id
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
myViewHolder hv;
if (null==convertView){
hv = new myViewHolder();
convertView = inflater.inflate(R.layout.adapter_dialog_view,null,false);
hv.tvName = convertView.findViewById(R.id.tvName);
hv.tvCountry = convertView.findViewById(R.id.tvCountry);
hv.agree = convertView.findViewById(R.id.agree);
hv.disAgree = convertView.findViewById(R.id.disAgree);
convertView.setTag(hv);
}else {
hv = (myViewHolder)convertView.getTag();
}
//一定要判刑断下数据源是否为空,否则很大几率就crash了
if (mapList!=null && !mapList.isEmpty()){
hv.tvName.setText(mapList.get(position).get("name"));
hv.tvCountry.setText(mapList.get(position).get("country"));
//设置监听事件
hv.agree.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, mapList.get(position).get("name"), Toast.LENGTH_SHORT).show();
}
});
//设置监听事件
hv.disAgree.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, mapList.get(position).get("country"), Toast.LENGTH_SHORT).show();
}
});
}
return convertView;
}
class myViewHolder{
TextView tvName;
TextView tvCountry;
Button agree;
Button disAgree;
}
}
02.创建列表对话框
public class MainActivity extends AppCompatActivity {
private DialogListAdapter listAdapter;
private List<HashMap<String, String>> lists = new ArrayList<>();
String[] names = {"Tom", "jane", "kangkang", "mike"};
String[] countries = {"china", "japan", "germany", "usa"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViewDatas();
findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
show_adapter(v);
}
});
}
/**
* 初始化adapter并填充数据
*/
void initViewDatas() {
listAdapter = new DialogListAdapter(this, lists);
for (int i = 0; i < 10; i++) {
HashMap<String, String> map = new HashMap<>();
map.put("name", names[i % 4]);
map.put("country", countries[i % 4]);
lists.add(map);
}
listAdapter.notifyDataSetChanged();
}
//这是一个button的onclick事件
public void show_adapter(View view) {
AlertDialog.Builder adapterBuilder = new AlertDialog.Builder(this);
adapterBuilder.setIcon(R.mipmap.ic_launcher_round);
adapterBuilder.setTitle("This is Adapter Dialog");
ListView listView = new ListView(this);
adapterBuilder.setView(listView);
listView.setAdapter(listAdapter);
//设置适配器,设置监听事件,但是点击后会退出对话框
// adapterBuilder.setAdapter(listAdapter, new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// HashMap<String, String> selectMap = (HashMap<String, String>) listAdapter.getItem(which);
// Toast.makeText(MainActivity.this, selectMap.get("name"), Toast.LENGTH_SHORT).show();
// }
// });
adapterBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//do what you want
}
});
adapterBuilder.setNegativeButton("Cancle", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//do what you want
}
});
adapterBuilder.show();
}
}
总结
终于完成这份总结,虽然还没有囊括所有的Android自定义控件,但这只是开始,后续我还会持续更新更多有关Android自定义控件的内容,欢迎大家点赞、关注、收藏!!!