Android 自定义View——自定义一个文本选择框

Android WebView 长按弹出的文本选择器如何监听滑动和如何弹出完全自定义的菜单栏 研究了一天半终于实现这个功能了,确实不容易呀,然后就开始测试发现竟然有bug,就是位置竟然不对劲,比如我从第三行开始选择,我拉都第二行拉到第一行,看着对劲,然后再从第一行拉到第二行的时候竟然位置不对了,怎么回事!现在我要实现的是长按webview后弹出来的拉选框(就是两个可以滑动的图标、用来选择文字的)要用android系统自带的,然后弹出的菜单栏要我们android自己自定义的弹框(以前就是自定义,许多功能都是写好的,我们还有单击也会弹这个框)。找到问题该怎么解决啊,难道是系统bug? 阅读详情

项目地址:项目地址包含之前的内容
图片预览
这种效果也算是比较常用的选择方式了。

  • View的绘制流程
  • 自定义View代码示例

View的绘制流程

//DecorView将会调用
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: CustomFrameLayout
07-10 11:33:18.657 23998-23998/com.example.study E/CustomButton: CustomButton2
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout

//CustomFrameLayout的onMeasure调用
07-10 11:33:18.677 23998-23998/com.example.study E/CustomButton: onMeasure
07-10 11:33:18.677 23998-23998/com.example.study E/CustomFrameLayout: onMeasure

//CustomFrameLayout的onLayout调用CustomButton的layout,然后由CustomButton的layout调用onLayout
07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onLayout
07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: layout
07-10 11:33:18.707 23998-23998/com.example.study E/CustomFrameLayout: onLayout

//CustomFrameLayout的onDraw调用CustomButton的onDraw
07-10 11:33:18.707 23998-23998/com.example.study E/CustomFrameLayout: onDraw
07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onDraw
07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onDraw

//CustomFrameLayout的onMeasure调用CustomButton的onMeasure
07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: onMeasure
07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onMeasure
07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: onLayout
07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: layout
07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onLayout
07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onDraw
07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: onDraw

CustomFrameLayout继承自FrameLayout,CustomButton继承自Button。而且只有CustomButton是CustomFrameLayout子view。

ViewGroup中的onMeasure、onLayout、onDraw。会遍历自己的子view分别调用onMeasure、layout、onDraw方法。

自定义View代码示例

public class CustomView extends View {
    private static final String TAG = "CustomView";

    /**
     * 文字之间的间隔
     */
    private final int TEXT_LINE_SPACE = 40;

    /**
     * 文字和线之间的间隔
     */
    private final int LINE_SPACE = TEXT_LINE_SPACE / 2;
    /**
     * 默认的文字大小
     */
    private final float DEFAULT_TEXT_SIZE = 32;

    /**
     * 标题
     */
    private List<String> mTitles;
    /**
     * 画线的画笔
     */
    private Paint mLinePaint;

    /**
     * 画标题的画笔
     */
    private Paint mTitlePaint;

    /**
     * view的中心点
     */
    int mCenterX;
    int mCenterY;

    /**
     * 文本中心点初始值Y
     */
    int mCenterTextDefalutY;

    /**
     * 文本的中心点Y
     */
    int mCenterTextY;

    /**
     * 当前文本的下标
     */
    private int currentIndex;
    /**
     * 文本高度
     */
    private int titleHeight;
    /**
     * 文本和行间距的高度
     */
    private int textTotalHeight;

    /**
     * 是否允许滑动超出范围
     */
    private boolean disAllowOutTopOrBottom = true;

    /**
     * 当前手指所在的坐标
     */
    float x;
    float y;


    /**
     * @param context
     */
    public CustomView(Context context) {
        this(context, null);
    }

    public CustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);

    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mTitles = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            mTitles.add("第 " + i + " 个标题");
        }

        //划线的画笔
        mLinePaint = new Paint();

        mLinePaint.setColor(Color.GRAY);
        //画标题的画笔
        mTitlePaint = new Paint();
        mTitlePaint.setTextSize(DEFAULT_TEXT_SIZE);
        mTitlePaint.setTextAlign(Paint.Align.CENTER);

        //文本高度
        titleHeight = (int) DEFAULT_TEXT_SIZE;
        //文本和行间距高度
        textTotalHeight = titleHeight + TEXT_LINE_SPACE;

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int viewWidth = MeasureSpec.getSize(widthMeasureSpec);
        int viewHeight = MeasureSpec.getSize(heightMeasureSpec);
        mCenterX = viewWidth / 2;
        mCenterY = viewHeight / 2;

        mCenterTextDefalutY = mCenterY;

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e(TAG, "onMeasure");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        Log.e(TAG, "onLayout");
        super.onLayout(changed, left, top, right, bottom);

    }

    @Override
    public void layout(int l, int t, int r, int b) {
        Log.e(TAG, "layout");
        super.layout(l, t, r, b);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        Log.e(TAG, "onDraw");
        super.onDraw(canvas);

        //没有数据就返回
        if (mTitles.size() == 0) {
            return;
        }
        if (currentIndex < 0) {
            currentIndex = 0;
        }
        if (currentIndex >= mTitles.size()) {
            currentIndex = mTitles.size() - 1;
        }


        //整体的偏移量
        int offset = mCenterY - mCenterTextDefalutY;
        //偏移了多少个文本的高度

        int i = offset / textTotalHeight;

        int i1 = offset % (textTotalHeight);
        //如果超过余下的,大于一个高度,就加一
        if (i1 > textTotalHeight / 2) {
            i++;
        }
        currentIndex = i;
        Log.e("index", "currentIndex:  " + currentIndex);


        mCenterTextY = mCenterTextDefalutY;
        for (String title : mTitles) {
            int index = mTitles.indexOf(title);
            if (index == currentIndex) {
                //当前文本设置颜色
                mTitlePaint.setColor(Color.BLACK);
            } else {
                //其它文本设置颜色
                mTitlePaint.setColor(Color.GRAY);
            }
            canvas.drawText(title, mCenterX, mCenterTextY + (float) titleHeight / 4, mTitlePaint);
            if (title.equals(mTitles.get(mTitles.size() - 1))) {
                break;
            }
            mCenterTextY += titleHeight + TEXT_LINE_SPACE;
        }
        //上面的线
        drawTopLine(canvas, titleHeight);

        drawBottomLine(canvas, titleHeight);
    }

    /**
     * 绘制上方的横线
     *
     * @param canvas
     * @param titleHeight
     */
    private void drawTopLine(Canvas canvas, float titleHeight) {
        float startX = 0;
        float startY = mCenterY - titleHeight / 2 - LINE_SPACE;
        float stopX = mCenterX * 2;
        canvas.drawLine(startX, startY, stopX, startY, mLinePaint);
    }

    /**
     * 绘制下方的横线
     *
     * @param canvas
     * @param titleHeight
     */
    private void drawBottomLine(Canvas canvas, float titleHeight) {
        float startX = 0;
        float startY = mCenterY + titleHeight / 2 + LINE_SPACE;
        float stopX = mCenterX * 2;
        canvas.drawLine(startX, startY, stopX, startY, mLinePaint);
    }

    /**
     * 获取文本区域所在的矩形区域,帮助获取绘制范围宽高
     *
     * @param title
     * @return
     */
    private Rect getTextBounds(String title) {
        Rect bounds = new Rect();
        mTitlePaint.getTextBounds(title, 0, title.length(), bounds);
        return bounds;
    }

    private int getTextWidth(String title) {
        Rect textBounds = getTextBounds(title);
        return textBounds.right - textBounds.left;
    }

    private int getTextHeight(String title) {
        Rect textBounds = getTextBounds(title);
        return textBounds.bottom - textBounds.top;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e(TAG, "dispatchTouchEvent" + "  action: " + event.getAction());
        return super.dispatchTouchEvent(event);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent");

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            x = event.getX();
            y = event.getY();
            return true;
        }
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            //和上次比划过的距离
            float v = event.getY() - y;
            y = event.getY();
            if (checkIsAllowOut(v)) {
                return true;
            }
            mCenterTextDefalutY += v;
            invalidate();
            return true;
        }
        if (event.getAction() == MotionEvent.ACTION_UP) {
            //防止滑出了第一条
            if (mCenterTextDefalutY - mCenterY >= 0) {
                //默认的初始文本中心和view中心相同或者大于就是在第一条
                mCenterTextDefalutY = mCenterY;
                invalidate();
                return true;
            }
            //防止滑出了最后一条
            if (mCenterTextY - mCenterY <= 0) {
                mCenterTextDefalutY = mCenterY - ((mTitles.size() - 1) * textTotalHeight);
                invalidate();
                return true;
            }


            mCenterTextDefalutY = mCenterY - (currentIndex * textTotalHeight);
            invalidate();


            return true;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 检查是否允许超出顶部和底部
     *
     * @param v
     * @return
     */
    private boolean checkIsAllowOut(float v) {
        if (!disAllowOutTopOrBottom) {
            return false;
        }
        if (v > 0) {
            //已经显示的是第一条,禁止下滑
            if (mCenterTextDefalutY - mCenterY >= 0) {
                //默认的初始文本中心和view中心相同或者大于就是在第一条
                mCenterTextDefalutY = mCenterY;
                invalidate();
                return true;
            }
        }

        if (v < 0) {
            //已经显示的是最后一条,禁止上滑
            Log.e(TAG, "mCenterTextY:  " + mCenterTextY);
            Log.e(TAG, "mCenterY:  " + mCenterY);
            Log.e(TAG, "mCenterTextDefalutY:  " + mCenterTextDefalutY);
            if (mCenterTextY - mCenterY <= 0) {
                mCenterTextDefalutY = mCenterY - ((mTitles.size() - 1) * textTotalHeight);
                invalidate();
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean performClick() {
        Log.e(TAG, "performClick");
        return super.performClick();
    }
}

整体思路:

  1. 确定view的中心位置。mCenterY
  2. 根据view中心位置mCenterTextDefalutY ,确定第一个文本的中心位置。然后通过确定的文本高度和行间距,一次往下画出其它文本。
  3. 根据view的中心位置和文本高度确定两天线的位置。
  4. 事件处理,计算滑动距离,改变mCenterTextDefalutY (第一个文本中心位置)的值。
  5. 一些极限处理。
  6. 根据偏移的距离得到偏移了多少个高度,得到当前应该显示的正确位置。在UP的时候要进行检查。

其它知识点、Paint、canvas等。

Android自定义View之区块选择器 最近撸了一个自定义view,还是比较复杂的,感觉有必要分享下实现的过程。 效果 先来看下效果吧: 我们来分析这个view需要实现哪些效果。 + 首先它有一个刻度尺代表了时间段(也可以是别的什么),并且可以看到完整的刻度尺是比屏幕宽度大的,因此肯定需要可以左右滑动。 + 其次,可以有不可选的区域(gif中灰色块)和选中的区域(gif中蓝色块),点击刻度的空白位置出现或者移动选中区域... 阅读详情

相关推荐

Android实现自定义相机系列(1)—自定义view裁剪控件

目标 本系列文章主要记录自定义相机拍照系列,包括: 1、使用Android的Camera API自定义拍照模式,例如人脸拍照,OCR拍照等; 2、对拍完的照片进行裁剪; 3、自定义图片伸缩view,使用手势对图片进行放大缩小操作; 本人写博客也是为了自己学习和分享,也会参考网络上许多文章和开源项目,希望大家一块学习进步。这一篇文章主要介绍自定义裁剪view控件。 ...

renzhongrui的博客 2707

android中自定义下拉框

android自带的下拉框好用不?我觉得有时候好用,有时候难有,项目规定这样的效果,自带的控件实现不了,那么只有我们自己来老老实实滴写一个新的了,其实最基本的下拉框就像一些资料填写时,点击的时候出现在编辑框的下面,然后又很多选项的下拉框,可是我在网上找了一下,没有这种下拉框额,就自己写了一个,看效果图先: ,这个是资料填写的一部分界面,三个下拉框,选择故乡所在地; 点击之后弹出下拉框,选择

a497393102的专栏 6万+

利用自定义View实现头像截取页面

那么,如何制作这样一个效果呢,关键点在哪里呢? 做法其实可以有多种, 1)可以直接继承ImageView,然后在ImageView的OnDraw函数中直接绘制圆形或者方形的高亮圈; 2)也可以直接继承View,自己将Bitmap给传进来,在onDraw函数中先绘制图片,接着利用Path来绘制这个圆形或者方形的选择区域,利用Region.OP.DIFFERENCE来反向获取蒙板层。 不过利用clipPath来实现这个蒙板层的绘制,画出来的圆形会有锯齿,这个目前我还没有找到比较好的办法去掉这个锯齿,不知道有没有

持剑 1766

从零开始实现一个兼容版本的Android文件选择器

此项目和之前发布的项目有些不同,之前都是基本的功能,不是基于 Activity 页面实现的,而类似文件选择,图片选择,除了功能的实现还需要处理 UI 相关的配置。在前面的一文中我就想把逻辑做一下封装,做成开箱即用的文件选择器,本来这功能是项目中自用的,UI 等都是自有的,如果要做开源出去,那么就要抽取功能与 UI 逻辑,设置可配置选项。分解一下实现步骤,如何自定义一个文件下载器呢?这样差不多就能完成一个基本的操作文件选择框架了。

chuyouyinghe的专栏 2616

自定义单选,多选对话框(android)

史上最强的自定义单选多选对话框 可作为UI模板应用于项目中 少量改动即可实现定制

Android-HorizontalPicker支持文本和图标的水平选择器

HorizontalPicker:支持文本和图标的水平选择器

android 文本框设置全选

android 文本框设置全选

qq_51303254的博客 604

Android文件对话框的代码例子

Android文件对话框的代码例子,用于演示自定义对话框的步骤与用法。示例代码中实现了如下对话框:日期选择对话框、时间选择对话框、信息确认对话框、文件打开对话框、文件保存对话框

Android 自定义应用选择器对话框

实现一个自定义应用列表对话框以选择并预览文件

特立独行的博客 1283

Android自定义弹出对话框

Android自定义弹出对话框

手把手教你用闲置安卓手机+蓝鲸支付,零成本搞定个人项目自动收款(附私有化部署教程)

本文详细介绍了如何利用闲置安卓手机和蓝鲸支付系统,零成本搭建个人项目自动收款解决方案。通过私有化部署教程,读者可以学习到从手机选择、系统配置到支付渠道对接的全流程,实现安全、高效的收款系统,特别适合个人开发者和小型项目使用。

weixin_29597551的博客 335

Android 下拉选择框自定义view

首先来看一下实现的效果 基本思路:继承PopupWindow的自定义View 说明:下图这部分自己布局,本文中主要说明的是点击某个分类显示的下拉实现 第一步:自定义SpinnerPopuwindow继承PopupWindow /** * Created by sws on 2019-04-28. * from: * describe: */ public cla...

u010855711的博客 6261

忆阻器(二)——忆阻模拟器

除了真实的器件,我们也可以用运放和乘法器来搭忆阻模拟器。这节将介绍数学模型、matlab仿真及multisim仿真。 一、数学模型 忆阻分为磁控和荷控两种,这里介绍三次非线性磁控忆阻模型: {I=W(φ)VW(φ)=a+3bφ2dφdt=V \begin{cases} I=W(\varphi)V \\ W(\varphi)=a+3b \varphi ^2\\ \frac{d \varphi }{dt}=V \end{cases} ⎩⎪⎨⎪⎧​I=W(φ)VW(φ)=a+3bφ2dtdφ​=V​ 这里的WWW

http://www.icfgblog.com/ 1万+

Android 自定义控件+下拉选择框实现

自定义控件+下拉选择框实现 当系统自带的控件不能满足我们的需求,而我们又会经常使用到,这个时候我们就可以自定义控件。下拉框是我们在做项目中经常会需要用到的。 上图就是一个自定义的控件 点击显示下拉框进行选择: pop_view.xml: (自定义控件界面) <LinearLayout xmlns:android="http://schemas.android.com/apk/...

weixin_44478215的博客 3401

如何用Python+Librosa实现轴承异响检测?5步搞定工业设备故障预警

本文详细介绍了如何利用Python和Librosa库,通过5个步骤构建工业轴承异响检测与故障预警系统。从音频采集、预处理、频谱特征提取,到构建机器学习模型并进行实时监测,提供了完整的代码级解决方案。该方法能有效识别轴承早期故障特征,实现低成本、高效的设备预测性维护。

rnn9storyteller的博客 633

《Linux运维总结:Ubuntu22.04忘记root密码解决方案》

《Linux运维总结:Ubuntu22.04忘记root密码解决方案》

东城绝神 4460

Android自定义下拉选择框

一、效果:本Demo主要是一个自定义的下拉选择框,下拉框可以显示数据,也可以删除数据,类似QQ登陆的EditText。二、编码(1)主布局相对比较简单,用一个EditText和ImageButton组合而成,而下拉选择列表是用ListView。<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://sc

machenshuang的博客 3054

Nvidia Tesla A100 驱动安装方法

Nvidia Tesla A100 驱动安装方法 遇到的问题:以前安装tesla系列、geforce系列等gpu 驱动的时候,只需执行nvidiaxxxx.run驱动包或者安装nvidiaxxxx.rpm包即可,但遇到新的GPU Nvidia Tesla A100的时候,安装完驱动似乎不起作用,找了半天资料,原来需要以下步骤才可以使用;

qq_24343177的博客 2万+

Android实现点击出现选择框,或者长按显示选择界面(附带源码)

Android实现点击出现选择框,或者长按显示选择界面(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 865

2.3 MMU Notifier(上):KVM 催生的页表变化通知

探讨了MMU Notifier机制的作用及其在虚拟化等场景中的应用。当CPU页表发生变更(如munmap、页面迁移等操作)时,需要通知拥有"第二套页表"的子系统(如KVM的EPT/NPT、IOMMU、GPU等)失效其映射。MMU Notifier通过订阅-通知机制实现这一功能,核心数据结构包括mmu_notifier订阅对象和回调接口mmu_notifier_ops。当CPU页表修改时,通过mmu_notifier_range描述变更范围,调用订阅者的回调函数进行同步,确保外部MMU与CPU页表一致性。

图形/计算GPU的驱动与虚拟化技术分享园地 1037

【深度学习驱动流体力学】完整配置安装 OpenFOAM 及其所需的ThirdParty与QT5工具

针对以上文件及文件目录介绍其功能特性:Allclean:这是一个脚本或命令,通常用于清理(即删除)所有编译生成的临时文件和目标文件。执行后,可以将编译过程中生成的中间文件和已编译的程序删除,以便重新开始编译或者清理空间。Allwmake:这是一个脚本或命令,用于编译并安装ThirdParty-10目录中的所有软件包。执行此命令会遍历所有子目录,编译其中的软件包并将它们安装到指定位置。这是一个脚本或命令,用于编译和安装Libccmio库。

源代码杀手的博客 1998

android 弹出选择框,简单通用弹出选择框

[java] view plain copy LinearLayout ll =(LinearLayout)getActivity().findViewById(R.id.log_sel_qyport);           ll.setOnClickListener(new View.OnClickListener() {

wumingwuxin的博客 1万+

AUTOSAR进阶图解==>AUTOSAR_SWS_DefaultErrorTracer

AUTOSAR Default Error Tracer (DET)是AUTOSAR架构中的核心错误追踪模块,提供统一的错误报告机制。DET能够处理开发错误、运行时错误和瞬态故障三类问题,支持模块级和实例级的配置管理。模块架构上,DET通过Det_API接口与其他模块交互,可配置转发错误至诊断事件管理器(DEM)或调试日志追踪(DLT)。其配置结构包含全局参数和模块实例参数,允许灵活控制错误报告行为。DET在AUTOSAR 4.3.0版本中从DevelopmentErrorTracer扩展而来,增强了错误处

一名留德华的,康德的信徒的,专注于汽车电子领域的博客 910

android 选择框,一款Android的输入选择框

1 importandroid.content.Context;2 importandroid.graphics.Color;3 importandroid.graphics.Paint;4 importandroid.text.Editable;5 importandroid.text.TextWatcher;6 importandroid.view.Gravity;7 importandroi...

weixin_29981095的博客 1169

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

自定义下拉选择框 PopupWindow

自定义下拉选择框 PopupWindow 直接上代码:activity_main.xml &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" x...

tiger_gy的博客 471

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

上一篇: android View——事件分发
下一篇: Android JetPack——Lifecycle
彼天
博客等级 码龄11年 37粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值