Android之自定义ListView(一)_android 自定义listview
haoteby 2025-09-18 00:16 3 浏览
PS:自定义View是Android中高手进阶的路线.因此我也打算一步一步的学习.看了鸿洋和郭霖这两位大牛的博客,决定一步一步的学习,循序渐进.
学习内容:
1.自定义View实现ListView的Item左右滑动显示和隐藏弹窗的效果
自定义View其实是在Android学习路上比较难掌握的一个重要点,但是也是高手的必经之路,自定义View分为很多种,我们可以直接继承View,或者是继承他的直接子类或间接子类.ViewGroup,ListView,LinearLayout,Button等等.继承他们的间接子类还算是比较简单的..因为View的子类或者是间接子类可以帮助我们做很多的事情.有很多的地方,我们可以不去实现,子类就帮我们做了(onMeasure,onLayout,onDraw)这些方法.直接继承View,我们是必须要对这些方法进行重写的,来实现我们自定义View.
因此我这里就没有去直接继承View,而是选择继承了他的子类.由于ListView的使用还是相当的广泛的,也是有些费劲的.因此决定从ListView开始..这里先把自定义的ListView代码先粘出来.然后再一步一步的分析.
package com.view; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupWindow; import com.example.administrator.myview.R; /** * Created by totem on 2016/7/10. * @author 代码丶如风 */ public class MyListView extends ListView { private static final String TAG = "ListView"; private LayoutInflater inflater; /** * 手指按下的x,y坐标,以及移动以后的x,y坐标 */ private int xDown; private int yDown; private int xMove; private int yMove; private boolean isRightSliding; private boolean isLeftSliding; //滑动的最小距离 private int touchSlop; //PopWindow弹窗 private PopupWindow popupWindow; private int popWindowWidth; private int popWindowHeight; private Button delButton; private int mCurrentViewPosition; private View mCurrentView; //回调接口 private DeleteItemListener deleteItemListener; /** * 初始化操作 */ public MyListView(Context context, AttributeSet attrs) { super(context, attrs); inflater = LayoutInflater.from(context); touchSlop = ViewConfiguration.get(context).getScaledTouchSlop; View view = inflater.inflate(R.layout.delete_item, null); delButton = (Button) view.findViewById(R.id.id_item_btn); popupWindow = new PopupWindow(view, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); popupWindow.getContentView.measure(0, 0); popWindowWidth = popupWindow.getContentView.getMeasuredWidth; popWindowHeight = popupWindow.getContentView.getMeasuredHeight; } @Override public boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction; int x = (int) event.getX; int y = (int) event.getY; switch (action) { case MotionEvent.ACTION_DOWN: xDown = x; yDown = y; if (popupWindow.isShowing) { dismissPopWindow; } mCurrentViewPosition = pointToPosition(xDown, yDown); View view = getChildAt(mCurrentViewPosition - getFirstVisiblePosition); mCurrentView = view; break; case MotionEvent.ACTION_MOVE: xMove = x; yMove = y; int offsetX = xDown - xMove; int offsetY = yDown - yMove; if (xMove < xDown && Math.abs(offsetX) > touchSlop && Math.abs(offsetY) < touchSlop) { isLeftSliding = true; }else if(xMove >xDown && Math.abs(offsetX) >touchSlop && Math.abs(offsetY) < touchSlop){ isRightSliding = true; } break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction; if (isLeftSliding) { switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: int location = new int[2]; mCurrentView.getLocationOnScreen(location); popupWindow.setAnimationStyle(R.style.popwindow_delete_btn_anim_style); //设置弹窗的动画效果 popupWindow.update; popupWindow.showAtLocation(mCurrentView, Gravity.LEFT | Gravity.TOP, location[0] + mCurrentView.getWidth, location[1] + mCurrentView.getHeight / 2 - popWindowHeight / 2); delButton.setOnClickListener(new OnClickListener { @Override public void onClick(View v) { if (deleteItemListener != null) { deleteItemListener.DeleteItem(mCurrentViewPosition); popupWindow.dismiss; } } }); break; case MotionEvent.ACTION_UP: isLeftSliding = false; break; } //防止与Item点击事件冲突 return true; }else if(isRightSliding){ switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: if(popupWindow.isShowing){ dismissPopWindow; } break; case MotionEvent.ACTION_UP: isRightSliding = false; break; } return true; } return super.onTouchEvent(event); } public void setDeleteItemListener(DeleteItemListener listener) { deleteItemListener = listener; } public interface DeleteItemListener { void DeleteItem(int position); } private void dismissPopWindow { if (popupWindow != null && popupWindow.isShowing) { popupWindow.dismiss; } } }
这里一部分是鸿洋大牛的.我又简单的优化了点.他写的只有向左滑动弹出,而没有向右滑动隐藏.因此我这里就给加上了.主要还是理解其中的思想才是关键.毕竟自定义View的学习没有什么相关的书籍,只能看这些牛人的博客了.看完也是受益匪浅的.废话不多说,我们总体缕一下思路,如何去实现这个自定义View是关键.
首先:
我们要清楚,用户的操作,怎样才算是滑动效果,怎样算是从左往右划,怎样算从右往左划,这也是问题的第一个关键所在,那么我们知道用户滑动的时候首先是需要点击屏幕的,因此这里定义了xDown和yDown来记录手指点下的坐标.那么滑动完以后,必然有一个结束滑动的坐标,xMove,yMove.相比到这里我们就明确怎样是向左滑动和向右滑动了.
xMove - xDown > 0 ? 向左滑动:向右滑动。。同时这里还要满足xMove - xDown要大于最小的滑动距离,这里最小的滑动距离就是用touchslop来记录的即:touchslop = ViewConfiguration.get(context).getScaledTouchSlop.这样我们就先解决了第一个问题,如何判断滑动.
其次:
滑动之后需要弹出一个窗口,那么这里就使用PopWindow来实现了.因此这里就创建了一个PopWindow.这个PopWindow可以显示出滑动后的删除按钮,因此在new的时候,需要把相关的view附加上.
/** * <p>Create a new non focusable popup window which can display the * <tt>contentView</tt>. The dimension of the window must be passed to * this constructor.</p> * * <p>The popup does not provide any background. This should be handled * by the content view.</p> * * @param contentView the popup's content * @param width the popup's width * @param height the popup's height */ public PopupWindow(View contentView, int width, int height) { this(contentView, width, height, false); }
源码是这样写的,创建一个无焦点的PopWindow用于显示我们传入的contentView.在这里这个PopWindow用于显示删除按钮.但是我们需要明确一个地方.这个PopWindow是我们new出来的,并没有在xml文件中进行书写,我们在获取它的宽高时,需要调用measure方法,先对这个PopWindow进行测量,测量之后我们才能够拿到相应的宽度和高度,因为这个PopWindow并没有在我们的ListView中,也没有在Item中,而是我们手动加上的,因此ListView在onMeasure的时候是不会对这个PopWindow进行测量的.这个取决于View的加载机制,这里我先不进行多说,等到后期我会补上View的加载机制,如果读者想现在弄明白怎么回事,推荐先去看看郭林大牛的博客,关于View的四篇文章,读完那四篇文章,就能够理解这块到底是怎么回事了.反正在这里读者只需要先记住就可以,不调用measure方法是拿不到宽高的.读者可以自己去试一下.好了,这样我们就解决了第二个问题.
最后:回调接口
首先,我们在MyView中引入了一个按钮,也就是PopWindow显示的按钮,但是这个按钮需要做事情,它需要在被点击的时候移除掉当前的Item,我们知道只有主线程才有权利对View进行操作,我们定义的这个View是没有权利的,这样就需要一个回调接口,在Button被点击的时候出发回调事件,触发的时候需要传递position参数,也就是当前Item的position.这个position传入后,被称为登记回调函数.触发的同时登记回调函数告知主线程这个事件被触发了,需要主函数进行处理,那么主线程在对Item进行操作,这样就符合规则了,因此回调函数这个概念想必大家就清楚了.
前一阵子在知乎上看到有解释这个的概念,觉得说的非常的合理,在这里放上,这样就更方便大家的理解了.
回调函数是什么?
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件.
然后事件分发机制了.dispatchTouchEvent和onTouchEvent,其实中间还有一个interpectTouchEvent,用于判断是否需要对事件进行拦截,这个就不说了,对ACTION_DOWN,ACTION_UP,ACTION_DOWN事件进行处理.dispatchTouchEvent用于分发事件,只在这里进行了简单的操作,对当前被点击的Item进行记录,以及对isRightSliding,isLeftSliding属性进行赋值,分发之后就交给了onTouchEvent去处理.它来完成对ACTION的处理.这样我们的总体思路就非常清晰了.
首先:对滑动事件的判断,其次:加入我们滑动后需要显示的效果,接着添加相应的回调函数用来处理View的触发事件,最后对事件分发处理,就解决了自定义View.最后附上MainActivity的源代码.
package com.example.administrator.myview; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import com.view.MyListView; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class MainActivity extends Activity { private MyListView myListView; private ArrayAdapter<String> adapter; private List<String> mData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView; } private void initView{ myListView = (MyListView) findViewById(R.id.MyListView); mData = new ArrayList<>(Arrays.asList("1","2","3","4","5","6","7","8","9","10")); adapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,mData); myListView.setAdapter(adapter); myListView.setDeleteItemListener(new MyListView.DeleteItemListener { @Override public void DeleteItem(int position) { adapter.remove(adapter.getItem(position)); } }); myListView.setOnItemClickListener(new AdapterView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //Item的点击事件 } }); } }
我们看到MainActivity实现回调接口,然后对View进行处理,与我们所说一致.这样就OK了,这里只简单的写了个Adapter,如果想写更复杂的就自己去实现Adapter了.
博客园的文件上传限制在10M,没有办法只能分享个百度云的链接了.
相关推荐
- 二次面试终拿到offer,百度Android面试真题解析我整理出来了
-
找工作的大潮来临了,这边给大家分享一下面试会遇到的问题。Android开发了5年,之前一直都是在小公司码着代码,对大厂一直有着憧憬,我是在去年年初的时候通过朋友的内推面试了百度,结果被怼的没话说,那叫...
- Android 开发中文引导-应用小部件
-
应用小部件是可以嵌入其它应用(例如主屏幕)并收到定期更新的微型应用视图。这些视图在用户界面中被叫做小部件,并可以用应用小部件提供者发布。可以容纳其他应用部件的应用组件叫做应用部件的宿主(1)。下面的截...
- Win10桌面/手机版最深层次开发功能挖掘
-
IT之家讯Win10开发者预览版为我们提供了一个Win10大框架的早期概览,使开发者与热心用户都可以提前感受Win10带来的新特性,尝试新工具,而作为开发者,最关心的莫过于Windows多平台通用应...
- Android TabLayout + ViewPager2使用
-
1、xml文件<!--明细列表--><com.google.android.material.tabs.TabLayoutandroid:id="@+id/ty_...
- android培训学习的大纲_android软件开发培训
-
第一阶段android基础:1.基础javaJava概述,进制,数据类型,常量变量,运算符,表达式关系运算符,逻辑运算符,if语句,switch语句while循环,do...while循环,for循环...
- 背了几十份面经还是连挂6个面试,最终拿下腾讯后总结了这些坑点
-
刚开始面试的时候我真的是处处碰壁,面一家挂一家,面完之后怀疑自我,是不是自己真的太菜了找不到工作。工作本身就是双向选择,一家不行再换一家,总有合适的,千万不要因为别人的一句话就全盘否定自己,一定要自信...
- webview 渲染机制:硬件加速方式渲染的Android Web
-
webview渲染是什么?webview渲染是用于展现web页面的控件;webview可以内嵌在移动端,实现前端的混合式开发,大多数混合式开发框架都是基于webview模式进行二次开发的...
- ExpandListView 的一种巧妙写法_仿写丁香结的写法写一种花梅花
-
ExpandListView大家估计也用的不少了,一般有需要展开的需求的时候,大家不约而同的都想到了它然后以前自己留过记录的一般都会找找以前自己的代码,没有记录习惯的就会百度、谷歌,这里吐槽一下,好几...
- Android监听滚动视图_滚动监听代码
-
AndroidUILibs之Android-ObservableScrollView1.说明Android-ObservableScrollView,顾名思义,Android上观察滚动的视图,可...
- Flutter 之 ListView_flutter开发
-
在Flutter中,ListView可以沿一个方向(垂直或水平方向)来排列其所有子Widget,常被用于需要展示一组连续视图元素的场景ListView构造方法ListView:仅适用于列表中...
- Android之自定义ListView(一)_android 自定义listview
-
PS:自定义View是Android中高手进阶的路线.因此我也打算一步一步的学习.看了鸿洋和郭霖这两位大牛的博客,决定一步一步的学习,循序渐进.学习内容:1.自定义View实现ListView的Ite...
- ListView 使用详解_listview在哪里
-
阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android本篇文章主要介绍Android开发中的部分知识点,通过阅读本篇文章,您将收获以下内容:ListView主要使用方法使用androi...
- 穿裙子的李宇春,需要向谁解释吗?
-
...
- 明星们“不想占用”的公共资源,到底是个啥?
-
近日,社交媒体上可谓“一瓜未平一瓜又起”,明星们隔三差五地在热搜上道歉、澄清,好不热闹。道歉原因千万种,道歉话术却雷同——通常都以“无意占用公共资源”开头,不管是澄清、官宣,还是回应、声明,都会带上这...
- 选择女人的模板,模板不同,生活方式不同
-
文/高阳人生在世,选择伴侣成了头等大事。尤其是对于男人来说,“选择女人的模板”直接决定了你未来几十年的生活方式,有时简直像组装拼图,每一块都有代价。莫言曾说过:“你选择能干的女人,就得接受她的强势;你...