仿qq底部的提示标记

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



<?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();
    }
}

源代码下载

posted @ 2018-04-20 16:45  zhchoutai  阅读(252)  评论(0编辑  收藏  举报