百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Android之自定义ListView(一)

wptr33 2025-05-23 20:38 23 浏览

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,没有办法只能分享个百度云的链接了.

相关推荐

MySQL进阶五之自动读写分离mysql-proxy

自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...

Postgres vs MySQL_vs2022连接mysql数据库

...

3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?

引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...

一文由浅入深带你完全掌握MySQL的锁机制原理与应用

本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...

验证Mysql中联合索引的最左匹配原则

后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础...

你会看 MySQL 的执行计划(EXPLAIN)吗?

SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...

MySQL 从入门到精通(四)之索引结构

索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...

mysql总结——面试中最常问到的知识点

mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...

mysql总结——面试中最常问到的知识点(2)

首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...

MySQL基础全知全解!超详细无废话!轻松上手~

本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...

深入剖析 MySQL 中的锁机制原理_mysql 锁详解

在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...

Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析

引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...

MySQL基础篇:DQL数据查询操作_mysql 查

一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...

MySql:索引的基本使用_mysql索引的使用和原理

一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...