Android自定义TipView仿QQ长按后的提示窗口

自定义view--TipView

TipView其实就是类似QQ长按消息弹出来的横放的提示框。

通过看书和参考各位大神的博客(再次对大神表示恭敬),我用了一下午时间写完了这么一个view。

先来看图:

1 自定义TipView思路

1 首先我们考虑是继承View还是ViewGroup

其实TipView直观看更像是一个group,里面有子view。但其实我们并不需要继承ViewGroup,因为我们不用像LinearLayout那样在布局文件里面去添加子view,而且TipView的item我们用文字就好。如果继承于Group我们还要考虑onLayout的问题,为了简单我直接继承自View。

2 重写方法

TipView要像PopupWindow、Dialog一样显示在Activity上而不是添加到父容器中,原因是如果创建后添加到父容器中去托管的话,父容器的布局规则会影响我们TipView的显示效果。所以我们要使用WindowManager来把TipView添加到外层布局,并且要充满屏幕,i原因为我们要点击tem之外的地方使TipView消失。所以view大小是固定充满屏幕的,不需要重写onMeasure。
需要重写onDraw来绘制view。

3 显示位置

TipView主要分两部分,一部分是三角标,一部分是带有圆角的主体。

当我们点击后,三角标顶点始终在点击位置上方一定距离(如果顶点定位在点击位置,会导致手指挡住一部分三角,用户体验度不佳),并且主体不要与屏幕左右边界碰撞,当要遮挡ToolBar时向下绘制。

2 定义变量

public static final int TOP = 0;//从点击位置上面绘制
  public static final int DOWN = 1;//...下面...

  private int mItemWidth;//item宽
  private int mItemHeight;//item高
  private int mTriaHeight;//三角的高度
  private int mHalfTriaWidth;//三角的半宽
  private int mTriaAcme;//三角的顶点
  private int mTriaItemBorder;//三角的顶点
  private int realLeft;//窗口距左边的值
  private int marginSide;//窗口距左右边的值,防止出现的窗口紧贴边界
  private int mSeparateLineColor = Color.WHITE;
  private int mTextSize;//选项文字的大小
  private int mTextColor;//选项文字的颜色

  private int mItemSeparation;//分割线宽度;
  private int mRadius;//圆角
  private List<TextItem> items;//存放item的集合
  private List<Rect> mItemRectList = new ArrayList<>(); // 存储每个方块
  private Paint mPaint;//画笔
  private Paint mSeparationPaint;//分割线画笔
  private Paint mSPaint;//三角的画笔
  private Path mPath;//路径
  private int x, y;//点击的位置
  private ViewGroup viewRoot;//父容器
  private int location = TOP;//绘制位置
  private int choose = -1;//点击的item
  private int mToolbarBottom;//Toolbar下边距屏幕上距离
  private WindowManager windowManager;
  private WindowManager.LayoutParams layoutParams;//windowManger布局管理器,为了像Dialog一样在Activity弹出,而不是依附于某个group
  private onItemCilckLinener itemCilckLinener;
  private Context context = null;

3 构造函数以及初始化方法

private MyTipView(Context context, int x, int y, ViewGroup viewRoot, List<TextItem> items) {
    super(context);
    this.viewRoot = viewRoot;
    this.context = context;
    this.x = x;
    this.y = y;
    this.items = items;
    windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    layoutParams = new WindowManager.LayoutParams();
    layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;//窗口的宽
    layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;//窗口的高
    //设置LayoutParams的属性
    layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;//该Type描述的是形成的窗口的层级关系,下面会详细列出它的属性
    layoutParams.format = PixelFormat.TRANSLUCENT;//不设置这个弹出框的透明遮罩显示为黑色
    //layoutParams.token = viewRoot.getWindowToken();//设置Token
    int[] location = new int[2];
    viewRoot.getLocationInWindow(location);//获取在当前窗口内的绝对坐标
    viewRoot.getLocationOnScreen(location);//获取在整个屏幕内的绝对坐标
    mToolbarBottom = location[1];//[0]是x轴坐标,[1]y轴
    windowManager.addView(this, layoutParams);
    init();
    initView();
  }

  //初始化画笔
  private void init() {
    mPaint = new Paint();
    mSPaint = new Paint();
    mPath = new Path();
    mSeparationPaint = new Paint();
    mSeparationPaint.setStyle(Paint.Style.FILL);
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setTextSize(Sp2Px(14));
    mPaint.setColor(Color.BLACK);
    mSPaint.setAntiAlias(true);
    mSPaint.setStyle(Paint.Style.FILL);
    mSPaint.setColor(Color.BLACK);
    //初始变量
    mItemWidth = Dp2Px(50);
    mItemHeight = Dp2Px(48);
    mTriaHeight = Dp2Px(10);//三角的高度
    mHalfTriaWidth = Dp2Px(6);//三角的半宽
    mTriaAcme = Dp2Px(6);//三角的顶点
    marginSide = Dp2Px(4);//左右边距
    mItemSeparation = Dp2Px(1);//分割线宽度;
    mRadius = Dp2Px(6);//圆角
    mTextColor = Color.WHITE;
    mTextSize = Sp2Px(14);
  }

4 计算三角顶点位置

private void initView() {
    int count = items.size();
    int width = count * mItemWidth + mItemSeparation * (count - 1);
    int mScreenWidth = getResources().getDisplayMetrics().widthPixels;
    if (y - mToolbarBottom < (mItemHeight + mTriaHeight + mTriaAcme)) {
      location = DOWN;//下方显示
      mTriaAcme += y;//设置三角顶点y轴值;
      mTriaItemBorder = mTriaAcme + mTriaHeight;//计算三角方块交界y
    } else {
      location = TOP;
      mTriaAcme = y - mTriaAcme;//计算顶点位置y轴值
      mTriaItemBorder = mTriaAcme - mTriaHeight;//计算三角方块交界y值
    }
    if (x < (width / 2 + marginSide)) {
      realLeft = marginSide;//计算最左侧距离屏幕左边距离,左边撑不下
    } else if ((mScreenWidth - x) < (width / 2 + marginSide)) {
      realLeft = mScreenWidth - marginSide - width;//计算最左侧距离屏幕左边距离,右边撑不下
    } else {
      realLeft = x - width / 2;//计算最左侧距离屏幕左边距离,触碰不到边界
    }
  }

5 设置背景为透明

private void drawBackground(Canvas canvas) {
    canvas.drawColor(Color.TRANSPARENT);
  }

6 绘制三角

private void drawTop(Canvas canvas) {
    //绘制三角
    mPath.reset();
    mPath.moveTo(x, mTriaAcme);
    mPath.lineTo(x - mHalfTriaWidth, mTriaAcme - mTriaHeight);
    mPath.lineTo(x + mHalfTriaWidth, mTriaAcme - mTriaHeight);
    canvas.drawPath(mPath, mSPaint);
    MyDraw(canvas, mTriaItemBorder - mItemHeight);
  }

  private void drawDown(Canvas canvas) {
    //绘制三角
    mPath.reset();//清理路径
    mPath.moveTo(x, mTriaAcme);
    mPath.lineTo(x - mHalfTriaWidth, mTriaAcme + mTriaHeight);
    mPath.lineTo(x + mHalfTriaWidth, mTriaAcme + mTriaHeight);
    canvas.drawPath(mPath, mSPaint);
    //绘制方块
    MyDraw(canvas, mTriaItemBorder);
  }

7 绘制方块

绘制时因为第一个和最后一个方块带有圆角,单独绘制

private void MyDraw(Canvas canvas, int t) {
    //绘制item
    int count = items.size();
    int width = (count - 1) * mItemSeparation + count * mItemWidth;
    int l = realLeft + mItemWidth + mItemSeparation;
    mItemRectList.clear();
    for (int i = 0; i < items.size(); i++) {
      if (choose == i) {//当前是否被点击,改变颜色
        mPaint.setColor(Color.DKGRAY);
      } else {
        mPaint.setColor(Color.BLACK);
      }
      if (i == 0) {//绘制第一个带圆角的item
        mPath.reset();
        mPath.moveTo(realLeft + mItemWidth, t);
        mPath.lineTo(realLeft + mRadius, t);
        mPath.quadTo(realLeft, t, realLeft, t + mRadius);
        mPath.lineTo(realLeft, t + mItemHeight - mRadius);
        mPath.quadTo(realLeft, t + mItemHeight, realLeft + mRadius, mItemHeight + t);
        mPath.lineTo(realLeft + mItemWidth, t + mItemHeight);
        canvas.drawPath(mPath, mPaint);
        mSeparationPaint.setColor(mSeparateLineColor);
        canvas.drawLine(realLeft + mItemWidth, t, realLeft + mItemWidth,
            t + mItemHeight, mSeparationPaint);
      } else if (i == (items.size() - 1)) {//绘制最后一个
        mPath.reset();
        mPath.rMoveTo(realLeft + width - mItemWidth, t);
        mPath.lineTo(realLeft + width - mRadius, t);
        mPath.quadTo(realLeft + width, t, realLeft + width, t + mRadius);
        mPath.lineTo(realLeft + width, t + mItemHeight - mRadius);
        mPath.quadTo(realLeft + width, t + mItemHeight, realLeft + width - mRadius, t + mItemHeight);
        mPath.lineTo(realLeft + width - mItemWidth, t + mItemHeight);
        canvas.drawPath(mPath, mPaint);
      } else {//绘制中间方块和分割线
        mPath.reset();
        mPath.moveTo(l, t);
        mPath.lineTo(l + mItemWidth, t);
        mPath.lineTo(l + mItemWidth, t + mItemHeight);
        mPath.lineTo(l, t + mItemHeight);
        canvas.drawPath(mPath, mPaint);
        canvas.drawLine(l + mItemWidth, t, l + mItemWidth, t + mItemHeight,
            mSeparationPaint);
        l += mItemWidth + mItemSeparation;
      }
      mItemRectList.add(new Rect(realLeft + i * (mItemSeparation + mItemWidth), t, realLeft + i * (mItemSeparation + mItemWidth) + mItemWidth, t + mItemHeight));
    }
  }

最后一行代码

代码如下:

mItemRectList.add(new Rect(realLeft + i * (mItemSeparation + mItemWidth), t, realLeft + i * (mItemSeparation + mItemWidth) + mItemWidth, t + mItemHeight));

用一个List来存放Rect(矩形),这些矩形对应的是每一个item的方块,但是并没有绘制出来,只是存放起来,矩形是为了在绘制文字的时候提供文字居中时用到的。

8 绘制文字

private void drawTitle(Canvas canvas) {
    for (int i = 0; i < items.size(); i++) {
      Rect rect = mItemRectList.get(i);//用于文字居中
      //mPaint.setColor(Color.WHITE);
      Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
      p.setAntiAlias(true);
      p.setStrokeWidth(3);
      int s = Dp2Px(items.get(i).getTextSize());
      p.setTextSize(mTextSize);
      if (s != 0)//如果在TextItem中设置了size,就是用设置的size
        p.setTextSize(s);
      p.setColor(mTextColor);
      Paint.FontMetricsInt fontMetricsInt = p.getFontMetricsInt();
      p.setTextAlign(Paint.Align.CENTER);
      int baseline = (rect.bottom + rect.top - fontMetricsInt.bottom - fontMetricsInt.top) / 2;//文字居中,基线算法
      canvas.drawText(items.get(i).getTitle(), rect.centerX(), baseline, p);
    }
  }

9 点击变色,以及点击事件实现

@Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        for (int i = 0; i < items.size(); i++) {
          if (itemCilckLinener != null && isPointInRect(new PointF(event.getX(), event.getY()), mItemRectList.get(i))) {
            choose = i;//记录点击item编号
            Rect rect = mItemRectList.get(i);
            postInvalidate(rect.left, rect.top, rect.right, rect.bottom);//刷新视图
            return true;
          }
        }
        removeView();//点击item以外移除
        return false;
      case MotionEvent.ACTION_UP:
        for (int i = 0; i < items.size(); i++) {
          if (itemCilckLinener != null && isPointInRect(new PointF(event.getX(), event.getY()), mItemRectList.get(i))) {
            if (i == choose) {//与down的item一样时才触发
              itemCilckLinener.onItemCilck(items.get(i).getTitle(), i);//触发点击事件
              removeView();
              return true;
            }
          } else {//点下后移动出item,初始化视图
            postInvalidate();//刷新视图
          }
        }
        choose = -1;//重置
        return false;
    }
    return false;
  }
 /**
   * 判断这个点有没有在矩形内
   *
   * @param pointF
   * @param targetRect
   * @return
   */
  private boolean isPointInRect(PointF pointF, Rect targetRect) {
    if (pointF.x < targetRect.left) {
      return false;
    }
    if (pointF.x > targetRect.right) {
      return false;
    }
    if (pointF.y < targetRect.top) {
      return false;
    }
    if (pointF.y > targetRect.bottom) {
      return false;
    }
    return true;
  }

10 Builder模式创建

public static class Builder {
    private List<TextItem> items = new ArrayList<>();
    private int x = 0, y = 0;
    private Context context;
    private ViewGroup viewRoot;
    private onItemCilckLinener itemCilckLinener;
    private int mRadius;

    public Builder(Context context, ViewGroup viewRoot) {
      this.context = context;
      this.viewRoot = viewRoot;
    }
    public Builder addItem(TextItem item) {
      items.add(item);
      return this;
    }
    public Builder setmRadius(int radius) {
      mRadius = radius;
      return this;
    }
    public Builder setxAndy(int x, int y) {
      this.x = x;
      this.y = y;
      return this;
    }
    public Builder setOnItemClickLinener(onItemCilckLinener itemClickLinener) {
      this.itemCilckLinener = itemClickLinener;
      return this;
    }
    public MyTipView create() {
      if (items.size() == 0) {
        try {
          throw new Exception("item count is 0");
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      MyTipView myTipView = new MyTipView(context, x, y, viewRoot, items);
      myTipView.setItemCilckLinener(itemCilckLinener);
      if (mRadius != 0)
        myTipView.setRadius(mRadius);
      return myTipView;
    }
  }

11 item

//TipView的item
  public static class TextItem {
    private String title;
    private int textSize;
    private int textColor = Color.WHITE;

    public TextItem(String title) {
      this.title = title;
    }
    public TextItem(String title, int textSize) {
      this.title = title;
      this.textSize = textSize;
    }
    public TextItem(String title, int textSize, int textColor) {
      this.title = title;
      this.textSize = textSize;
      this.textColor = textColor;
    }
    public String getTitle() {
      return title;
    }
    public void setTitle(String title) {
      this.title = title;
    }
    public int getTextSize() {
      return textSize;
    }
    public void setTextSize(int textSize) {
      this.textSize = textSize;
    }
    public int getTextColor() {
      return textColor;
    }
    public void setTextColor(int textColor) {
      this.textColor = textColor;
    }
  }

12 使用示例

MyTipView.Builder builder = new MyTipView.Builder(this, linearLayout);
    builder.addItem(new MyTipView.TextItem("1"))
        .addItem(new MyTipView.TextItem("2"))
        .addItem(new MyTipView.TextItem("3"))
        .addItem(new MyTipView.TextItem("4"))
        .setxAndy((int) x, (int) y)
        .setOnItemClickLinener(new MyTipView.onItemCilckLinener() {
          @Override
          public void onItemCilck(String title, int i) {
            Toast.makeText(MainActivity.this, title, Toast.LENGTH_SHORT).show();
          }
        })
        .create();

13 源码

https://github.com/liujiakuoyx/learn/blob/master/MyTipView.java

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android仿QQ长按弹出删除复制框

    本文实例为大家分享了Android仿QQ长按删除弹出框的具体代码,供大家参考,具体内容如下 废话不说,先看一下效果图: 对于列表来说,如果想操作某个列表项,一般会采用长按弹出菜单的形式,默认的上下文菜单比较难看,而QQ的上下文菜单就人性化多了,整个菜单给用户一种气泡弹出的感觉,而且会显示在手指按下的位置,而技术实现我之前是使用popupWindow和RecyclerView实现的,上面一个RecyclerView,下面一个小箭头ImageView,但后来发现没有必要,而且可定制化也不高,还是使用

  • Android仿QQ长按删除弹出框功能示例

    废话不说,先看一下效果图,如果大家感觉不错,请参考实现代码: 对于列表来说,如果想操作某个列表项,一般会采用长按弹出菜单的形式,默认的上下文菜单比较难看,而QQ的上下文菜单就人性化多了,整个菜单给用户一种气泡弹出的感觉,而且会显示在手指按下的位置,而技术实现我之前是使用popupWindow和RecyclerView实现的,上面一个RecyclerView,下面一个小箭头ImageView,但后来发现没有必要,而且可定制化也不高,还是使用多个TextView更好一点. 我封装了一下,只需要一个P

  • Android仿QQ、微信聊天界面长按提示框效果

    先来看看效果图 如何使用 示例代码 PromptViewHelper pvHelper = new PromptViewHelper(mActivity); pvHelper.setPromptViewManager(new ChatPromptViewManager(mActivity)); pvHelper.addPrompt(holder.itemView.findViewById(R.id.textview_content)); 使用起来还是很简单的 首先new一个PromptViewH

  • Android自定义TipView仿QQ长按后的提示窗口

    自定义view--TipView TipView其实就是类似QQ长按消息弹出来的横放的提示框. 通过看书和参考各位大神的博客(再次对大神表示恭敬),我用了一下午时间写完了这么一个view. 先来看图: 1 自定义TipView思路 1 首先我们考虑是继承View还是ViewGroup 其实TipView直观看更像是一个group,里面有子view.但其实我们并不需要继承ViewGroup,因为我们不用像LinearLayout那样在布局文件里面去添加子view,而且TipView的item我们用

  • Android自定义SwipeLayout仿QQ侧滑条目

    Android自定义SwipeLayout仿QQ侧滑条目,供大家参考,具体内容如下 先看动图 看布局文件 activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent

  • Android自定义View 仿QQ侧滑菜单的实现代码

    先看看QQ的侧滑效果 分析一下 先上原理图(不知道能否表达的清楚 ==) -首先这里使用了 Android 的HorizontalScrollView 水平滑动布局作为容器,当然我们需要继承它自定义一个侧滑视图 - 这个容器里面有一个父布局(一般用LinerLayout,本demo用的是),这个父布局里面有且只有两个子控件(布局),初始状态菜单页的位置在Y轴上存在偏移这样可以就可以形成主页叠在菜单页的上方的视觉效果:然后在滑动的过程程中 逐渐修正偏移,最后菜单页和主页并排排列.原理搞清了实现起来

  • Android自定义view仿QQ的Tab按钮动画效果(示例代码)

    话不多说 先上效果图 实现其实很简单,先用两张图 一张是背景的图,一张是笑脸的图片,笑脸的图片是白色,可能看不出来.实现思路:主要是再触摸view的时候同时移动这两个图片,但是移动的距离不一样,造成的错位感,代码很简单: import android.content.Context import android.graphics.* import android.util.AttributeSet import android.view.MotionEvent import android.vi

  • Android自定义View仿QQ健康界面

    最近一直在学习自定义View相关的知识,今天给大家带来的是QQ健康界面的实现.先看效果图: 可以设置数字颜色,字体颜色,运动步数,运动排名,运动平均步数,虚线下方的蓝色指示条的长度会随着平均步数改变而进行变化.整体效果还是和QQ运动健康界面很像的. 自定义View四部曲,一起来看看怎么实现的. 1.自定义view的属性: <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定

  • Android自定义View仿QQ运动步数效果

    本文实例为大家分享了Android QQ运动步数的具体代码,供大家参考,具体内容如下 今天我们实现下面这样的效果: 首先自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyQQStep"> <attr name="out_color" format="colo

  • Android自定义View仿QQ等级天数进度

    最近一直都在看自定义View这一块.差不多一个星期了吧.这个星期坚持每天更新博客,感觉自己的技术也有点突破,对自定义View的计算也有了更深的认识. 今天看到手机一个成长天数进度的控件,觉得挺有意思的,于是想自己也写一个.效果如下: 由图可以知道,这里面有很多个元素,首先是背景的矩形区域,其次就是两个环形,然后三个Text文本.其实不复杂,我们一点一点的去实现. 首先呢,画矩形背景.这里用到一个RectF的类,这个类包含一个矩形的四个单精度浮点坐标.矩形通过上下左右4个边的坐标来表示一个矩形.这

  • Android 自定义view仿微信相机单击拍照长按录视频按钮

    Android仿微信相机的拍照按钮单击拍照,长按录视频.先上效果图. 项目地址:https://github.com/c786909486/PhotoButton2/tree/v1.0 添加依赖 allprojects { repositories { ... maven { url 'https://jitpack.io' } } } dependencies { compile compile 'com.github.c786909486:PhotoButton2:v1.1' } 长按效果分

随机推荐