看到一个比較不错的开源项目,分享给大家:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
> <View android:layout_width="match_parent" android:layout_height="match_parent"
android:background="#aabbcc"
/> <Button
android:id="@+id/main_btn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:background="#cc000000"
android:text="click me! "
android:textColor="@android:color/white"
android:textSize="20sp" > </Button> <com.wangjie.draggableflagview.DraggableFlagView
xmlns:dfv="http://schemas.android.com/apk/res/com.wangjie.draggableflagview"
android:id="@+id/main_dfv"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_margin="8dp"
dfv:color="#FF3B30"
/> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="jingwen3699"
android:textSize="33sp"
android:textColor="@android:color/black"
android:layout_centerInParent="true"
/> </RelativeLayout>

package com.wangjie.draggableflagview.sample;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.wangjie.draggableflagview.DraggableFlagView;
import com.wangjie.draggableflagview.R; public class MainActivity extends Activity implements DraggableFlagView.OnDraggableFlagViewListener, View.OnClickListener { private DraggableFlagView draggableFlagView; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.main_btn).setOnClickListener(this); draggableFlagView = (DraggableFlagView) findViewById(R.id.main_dfv);
draggableFlagView.setOnDraggableFlagViewListener(this);
draggableFlagView.setText("7"); } @Override
public void onFlagDismiss(DraggableFlagView view) {
Toast.makeText(this, "onFlagDismiss", Toast.LENGTH_SHORT).show();
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.main_btn:
draggableFlagView.setText("7");
break;
}
}
}
package com.wangjie.draggableflagview;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.*;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.BounceInterpolator;
import android.widget.RelativeLayout;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ValueAnimator;
import com.wangjie.androidbucket.log.Logger;
import com.wangjie.androidbucket.utils.ABAppUtil;
import com.wangjie.androidbucket.utils.ABTextUtil; /**n
* Author: wangjie
* Email: tiantian.china.2@gmail.com
* Github: https://github.com/wangjiegulu/DraggableFlagView
* Date: 12/23/14.
*/
public class DraggableFlagView extends View {
private static final String TAG = DraggableFlagView.class.getSimpleName(); public static interface OnDraggableFlagViewListener {
/**
* 拖拽销毁圆点后的回调
*
* @param view
*/
void onFlagDismiss(DraggableFlagView view);
} private OnDraggableFlagViewListener onDraggableFlagViewListener; public void setOnDraggableFlagViewListener(OnDraggableFlagViewListener onDraggableFlagViewListener) {
this.onDraggableFlagViewListener = onDraggableFlagViewListener;
} public DraggableFlagView(Context context) {
super(context);
init(context);
} private int patientColor = Color.RED; public DraggableFlagView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DraggableFlagView);
int indexCount = a.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attrIndex = a.getIndex(i);
if (attrIndex == R.styleable.DraggableFlagView_color) {
patientColor = a.getColor(attrIndex, Color.RED);
}
}
a.recycle();
init(context);
} public DraggableFlagView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
} private Context context;
private int originRadius; // 初始的圆的半径
private int originWidth;
private int originHeight; private int maxMoveLength; // 最大的移动拉长距离
private boolean isArrivedMaxMoved; // 达到了最大的拉长距离(松手能够触发事件) private int curRadius; // 当前点的半径
private int touchedPointRadius; // touch的圆的半径
private Point startPoint = new Point();
private Point endPoint = new Point(); private Paint paint; // 绘制圆形图形
private TextPaint textPaint; // 绘制圆形图形
private Paint.FontMetrics textFontMetrics; private int[] location; private boolean isTouched; // 是否是触摸状态 private Triangle triangle = new Triangle(); private String text = ""; // 正常状态下显示的文字 private void init(Context context) {
this.context = context; setBackgroundColor(Color.TRANSPARENT); // 设置绘制flag的paint
paint = new Paint();
paint.setColor(patientColor);
paint.setAntiAlias(true); // 设置绘制文字的paint
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(ABTextUtil.sp2px(context, 12));
textPaint.setTextAlign(Paint.Align.CENTER);
textFontMetrics = textPaint.getFontMetrics(); } RelativeLayout.LayoutParams originLp; // 实际的layoutparams
RelativeLayout.LayoutParams newLp; // 触摸时候的LayoutParams private boolean isFirst = true; @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Logger.d(TAG, String.format("onSizeChanged, w: %s, h: %s, oldw: %s, oldh: %s", w, h, oldw, oldh));
if (isFirst && w > 0 && h > 0) {
isFirst = false; originWidth = w;
originHeight = h; originRadius = Math.min(originWidth, originHeight) / 2;
curRadius = originRadius;
touchedPointRadius = originRadius; maxMoveLength = ABAppUtil.getDeviceHeight(context) / 6; refreshStartPoint(); ViewGroup.LayoutParams lp = this.getLayoutParams();
if (RelativeLayout.LayoutParams.class.isAssignableFrom(lp.getClass())) {
originLp = (RelativeLayout.LayoutParams) lp;
}
newLp = new RelativeLayout.LayoutParams(lp.width, lp.height);
} } @Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
super.setLayoutParams(params);
refreshStartPoint();
} /**
* 改动layoutParams后。须要又一次设置startPoint
*/
private void refreshStartPoint() {
location = new int[2];
this.getLocationInWindow(location);
// Logger.d(TAG, "location on screen: " + Arrays.toString(location));
// startPoint.set(location[0], location[1] + h);
try {
location[1] = location[1] - ABAppUtil.getTopBarHeight((Activity) context);
} catch (Exception ex) {
} startPoint.set(location[0], location[1] + getMeasuredHeight());
// Logger.d(TAG, "startPoint: " + startPoint);
} Path path = new Path(); @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT); int startCircleX = 0, startCircleY = 0;
if (isTouched) { // 触摸状态 startCircleX = startPoint.x + curRadius;
startCircleY = startPoint.y - curRadius;
// 绘制原来的圆形(触摸移动的时候半径会不断变化)
canvas.drawCircle(startCircleX, startCircleY, curRadius, paint);
// 绘制手指跟踪的圆形
int endCircleX = endPoint.x;
int endCircleY = endPoint.y;
canvas.drawCircle(endCircleX, endCircleY, originRadius, paint); if (!isArrivedMaxMoved) { // 没有达到拉伸最大值
path.reset();
double sin = triangle.deltaY / triangle.hypotenuse;
double cos = triangle.deltaX / triangle.hypotenuse; // A点
path.moveTo(
(float) (startCircleX - curRadius * sin),
(float) (startCircleY - curRadius * cos)
);
// B点
path.lineTo(
(float) (startCircleX + curRadius * sin),
(float) (startCircleY + curRadius * cos)
);
// C点
path.quadTo(
(startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2,
(float) (endCircleX + originRadius * sin), (float) (endCircleY + originRadius * cos)
);
// D点
path.lineTo(
(float) (endCircleX - originRadius * sin),
(float) (endCircleY - originRadius * cos)
);
// A点
path.quadTo(
(startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2,
(float) (startCircleX - curRadius * sin), (float) (startCircleY - curRadius * cos)
);
canvas.drawPath(path, paint);
} // 绘制文字
float textH = - textFontMetrics.ascent - textFontMetrics.descent;
canvas.drawText(text, endCircleX, endCircleY + textH / 2, textPaint); } else { // 非触摸状态
if (curRadius > 0) {
startCircleX = curRadius;
startCircleY = originHeight - curRadius;
canvas.drawCircle(startCircleX, startCircleY, curRadius, paint);
if (curRadius == originRadius) { // 仅仅有在恢复正常的情况下才显示文字
// 绘制文字
float textH = - textFontMetrics.ascent - textFontMetrics.descent;
canvas.drawText(text, startCircleX, startCircleY + textH / 2, textPaint);
}
} }
// Logger.d(TAG, "circleX: " + startCircleX + ", circleY: " + startCircleY + ", curRadius: " + curRadius);
} float downX = Float.MAX_VALUE;
float downY = Float.MAX_VALUE; @Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
// Logger.d(TAG, "onTouchEvent: " + event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouched = true;
this.setLayoutParams(newLp);
endPoint.x = (int) downX;
endPoint.y = (int) downY; changeViewHeight(this, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
postInvalidate(); downX = event.getX() + location[0];
downY = event.getY() + location[1];
// Logger.d(TAG, String.format("downX: %f, downY: %f", downX, downY)); break;
case MotionEvent.ACTION_MOVE:
// 计算直角边和斜边(用于计算绘制两圆之间的填充去)
triangle.deltaX = event.getX() - downX;
triangle.deltaY = -1 * (event.getY() - downY); // y轴方向相反。全部须要取反
double distance = Math.sqrt(triangle.deltaX * triangle.deltaX + triangle.deltaY * triangle.deltaY);
triangle.hypotenuse = distance;
// Logger.d(TAG, "triangle: " + triangle);
refreshCurRadiusByMoveDistance((int) distance); endPoint.x = (int) event.getX();
endPoint.y = (int) event.getY(); postInvalidate(); break;
case MotionEvent.ACTION_UP:
isTouched = false;
this.setLayoutParams(originLp); if (isArrivedMaxMoved) { // 触发事件
changeViewHeight(this, originWidth, originHeight);
postInvalidate();
if (null != onDraggableFlagViewListener) {
onDraggableFlagViewListener.onFlagDismiss(this);
}
Logger.d(TAG, "触发事件...");
resetAfterDismiss();
} else { // 还原
changeViewHeight(this, originWidth, originHeight);
startRollBackAnimation(500/*ms*/);
} downX = Float.MAX_VALUE;
downY = Float.MAX_VALUE;
break;
} return true;
} /**
* 触发事件之后重置
*/
private void resetAfterDismiss() {
this.setVisibility(GONE);
text = "";
isArrivedMaxMoved = false;
curRadius = originRadius;
postInvalidate();
} /**
* 依据移动的距离来刷新原来的圆半径大小
*
* @param distance
*/
private void refreshCurRadiusByMoveDistance(int distance) {
if (distance > maxMoveLength) {
isArrivedMaxMoved = true;
curRadius = 0;
} else {
isArrivedMaxMoved = false;
float calcRadius = (1 - 1f * distance / maxMoveLength) * originRadius;
float maxRadius = ABTextUtil.dip2px(context, 2);
curRadius = (int) Math.max(calcRadius, maxRadius);
// Logger.d(TAG, "[refreshCurRadiusByMoveDistance]curRadius: " + curRadius + ", calcRadius: " + calcRadius + ", maxRadius: " + maxRadius);
} } /**
* 改变某控件的高度
*
* @param view
* @param height
*/
private void changeViewHeight(View view, int width, int height) {
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (null == lp) {
lp = originLp;
}
lp.width = width;
lp.height = height;
view.setLayoutParams(lp);
} /**
* 回滚状态动画
*/
private void startRollBackAnimation(long duration) {
ValueAnimator rollBackAnim = ValueAnimator.ofFloat(curRadius, originRadius);
rollBackAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
curRadius = (int) value;
postInvalidate();
}
});
rollBackAnim.setInterpolator(new BounceInterpolator()); // 反弹效果
rollBackAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
DraggableFlagView.this.clearAnimation();
}
});
rollBackAnim.setDuration(duration);
rollBackAnim.start();
} /**
* 计算四个坐标的三角边关系
*/
class Triangle {
double deltaX;
double deltaY;
double hypotenuse; @Override
public String toString() {
return "Triangle{" +
"deltaX=" + deltaX +
", deltaY=" + deltaY +
", hypotenuse=" + hypotenuse +
'}';
}
} public String getText() {
return text;
} public void setText(String text) {
this.text = text;
this.setVisibility(VISIBLE);
postInvalidate();
}
}

源代码下载

仿qq底部的提示标记的更多相关文章

  1. 仿QQ底部切换(Fragment + Radio)

     第一步: activity_main.xml  布局文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/ ...

  2. Android高仿qq及微信底部菜单的几种实现方式

    最近项目没那么忙,想着开发app的话,有很多都是重复,既然是重复的,那就没有必要每次都去写,所以就想着写一个app通用的基本框架,这里说的框架不是什么MVC,MVP,MVVM这种,而是app开发的通用 ...

  3. wpf实现仿qq消息提示框

    原文:wpf实现仿qq消息提示框 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/huangli321456/article/details/5052 ...

  4. Android 高仿QQ滑动弹出菜单标记已读、未读消息

    在上一篇博客<Android 高仿微信(QQ)滑动弹出编辑.删除菜单效果,增加下拉刷新功能>里,已经带着大家学习如何使用SwipeMenuListView这一开源库实现滑动列表弹出菜单,接 ...

  5. 史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS。

    重要的话 开头说,not for the RecyclerView or ListView, for the Any ViewGroup. 本控件不依赖任何父布局,不是针对 RecyclerView. ...

  6. Socket实现仿QQ聊天(可部署于广域网)附源码(1)-简介

    1.前言 本次实现的这个聊天工具是我去年c#程序设计课程所写的Socket仿QQ聊天,由于当时候没有自己的服务器,只能在机房局域网内进行测试,最近在腾讯云上买了一台云主机(本人学生党,腾讯云有个学生专 ...

  7. 高仿QQ即时聊天软件开发系列之三登录窗口用户选择下拉框

    上一篇高仿QQ即时聊天软件开发系列之二登录窗口界面写了一个大概的布局和原理 这一篇详细说下拉框的实现原理 先上最终效果图 一开始其实只是想给下拉框加一个placeholder效果,让下拉框在未选择未输 ...

  8. android-改进&lt;&lt;仿QQ&gt;&gt;框架源代码

    该文章主要改动于CSDN某大神的一篇文章,本人认为这篇文章的面向对象非常透彻,以下分享例如以下可学习的几点: Android应用经典主界面框架之中的一个:仿QQ (使用Fragment, 附源代码) ...

  9. 自定义仿 QQ 健康计步器进度条

    自定义仿 QQ 健康计步器进度条 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:CircleProgress 文中如有纰漏,欢迎大家留言指出. 闲着没事,趁上班时间偷偷撸了 ...

随机推荐

  1. js 弹窗广告24小时显示一次

    弹窗24小时显示一次 https://www.w3cschool.cn/javascript/js-cookies.html 我们需要借助cookie来实现这个功能 function setcooki ...

  2. request.getRequestDispatcher("").forward()中文乱码

    即使jsp页面的编码已设为“UTF-8”,有中文的地方还是会出现乱码,但是用response.sendRedirect不会出现此问题. 解决方案一: 不使用PrintWriter out=respon ...

  3. OpenOCD Debug Adapter Configuration

    Correctly installing OpenOCD includes making your operating system give OpenOCD access to debug adap ...

  4. 孕龙逻辑分析仪 ZeroPlus Logic Analyzer

    Voltage Translation for Analog to Digital Interface ADC http://openschemes.com/2010/03/23/zeroplus-l ...

  5. 浅谈BFC和IFC

    先说说FC,FC的含义就是Fomatting Context.它是CSS2.1规范中的一个概念. 它是页面中的一块渲染区域.而且有一套渲染规则,它决定了其子元素将怎样定位.以及和其它元素的关系和相互作 ...

  6. HDU 1428 漫步校园 (BFS+优先队列+记忆化搜索)

    题目地址:HDU 1428 先用BFS+优先队列求出全部点到机房的最短距离.然后用记忆化搜索去搜. 代码例如以下: #include <iostream> #include <str ...

  7. iOS 项目一直在后台执行

    我后来是这么解决不知道行不行,能够长期的在后台执行 首先我在xx-info.plist 里的 "Required background modes" 里增加"App pr ...

  8. jQuery遍历刚创建的元素

    对于刚创建的元素,使用jQuery的each方法,有时候会不起作用.解决方案大致有2种: 1.刚创建完的时候,就使用each方法 $('#btn').on("click", fun ...

  9. iPhone开发中从一个视图跳到另一个视图有三种方法:

    iPhone开发中从一个视图跳到另一个视图有三种方法:   1.self.view addSubView:view .self.window addSubView,需要注意的是,这个方法只是把页面加在 ...

  10. iphone6/6+ 适配心得

    1.     文档综述 自iphone6/6+发布,ios屏幕分辨率的种类一下从2种变成了四种.对于以前很多手写UI,并且使用绝对坐标的UI,可能会发生异变,本文主要介绍在纯手写UI条件下,ios应用 ...