可添加浮动bubble的seekbar

可添加浮动bubble的seekbar

Android 其它控件

详细介绍

SeekBarDemoProject

版本1

继承seekbar,在seekbar中,拓展绘制范围;

在seekbar中,强行绘制了bubble

  • 结果: 不够优雅;绘制占用的无用区域过多;
  • 结论: 应该让绘制区域局限于仅仅需要的大小;

版本2

继承seekbar;

bubble在外部自定义后,利用传参的形式,作为外部的组件传递进来;

利用WindowManager控制其显示的位置;

  • 步骤1: 在onMeasure中,对各种参数进行初始化;
@Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();

        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        mLayoutParams = new WindowManager.LayoutParams();

        mViewBubble = (ViewGroup) mLayoutInflater.inflate(mBubbleLayoutId, (ViewGroup)getParent(), false);

        setBubbleTextView(mBubbleTvId);
        ViewGroup.LayoutParams bubbleLayoutParams = mViewBubble.getLayoutParams();

        mBubbleWidth = bubbleLayoutParams.width;
        mBubbleHeight = bubbleLayoutParams.height;

        mLayoutParams.gravity= Gravity.LEFT | Gravity.TOP;
        mLayoutParams.width = (int) mBubbleWidth;
        mLayoutParams.height = (int) mBubbleHeight;
        mLayoutParams.format = PixelFormat.TRANSLUCENT;
        mLayoutParams.flags  = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;

        /**
         * 这边似乎需要做一个版本号的判断
         */
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
        mWindowManager.addView(mViewBubble, mLayoutParams);
        mViewBubble.setVisibility(GONE);
    }

其中: mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight();,用此来获取seekbar的实际宽高;

原因是为了避免在外部设置控件宽高为match_parent或者wrap_content的时候,MeasureSpec的getSize方法,获取的是父控件的长宽,不合理;

mLayoutParams.gravity= Gravity.LEFT | Gravity.TOP : 将WindowManager的原点设置为 (0,0),便于位置的控制;

mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION; 普通应用功能程序窗口

参考:http://blog.csdn.net/listening_music/article/details/7169330

最后在windowManager上add mViewBubble;

  • 步骤2:
@Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:{
                float x = event.getRawX();
                int[] location = new int[2];
                this.getLocationInWindow(location);
                if (mBubbleAbove) {
                    mLocalY = location[1] - mHeight - mBubbleHeight + mBubbleOffsetPosition;
                }else {
                    mLocalY = location[1] + mBubbleOffsetPosition;
                }

                mLocalX = x - mBubbleWidth/2;
                showBubble(mLocalX, mLocalY);
                mViewBubble.setVisibility(VISIBLE);
                break;
            }
            case MotionEvent.ACTION_MOVE:{
                float x = event.getRawX();
                int[] location = new int[2];
                this.getLocationInWindow(location);
                if (mBubbleAbove) {
                    mLocalY = location[1] - mHeight - mBubbleHeight + mBubbleOffsetPosition;
                }else {
                    mLocalY = location[1] + mBubbleOffsetPosition;
                }
                /**
                 * 利用location计算出 控件在屏幕中的坐标
                 * getMeasuredWidth() 获得measure后的控件的实际宽度
                 */
                if (location[0] + mThumbOffset * 2 > x){
                    x = location[0] + mThumbOffset * 2;
                }else if (location[0] + mWidth - mThumbOffset * 2  < x){
                    x = location[0] + mWidth - mThumbOffset * 2;
                }
                mLocalX = x - mBubbleWidth/2;

                showBubble(mLocalX, mLocalY);
                break;
            }

            case MotionEvent.ACTION_UP:{
                mViewBubble.setVisibility(GONE);
                break;
            }
            case MotionEvent.ACTION_CANCEL:{
                mViewBubble.setVisibility(GONE);
                break;
            }
            default:{
                break;
            }
        }

        return super.onTouchEvent(event);
    }

核心代码:

				float x = event.getRawX();
                int[] location = new int[2];
                this.getLocationInWindow(location);
                if (mBubbleAbove) {
                    mLocalY = location[1] - mHeight - mBubbleHeight + mBubbleOffsetPosition;
                }else {
                    mLocalY = location[1] + mBubbleOffsetPosition;
                }
                /**
                 * 利用location计算出 控件在屏幕中的坐标
                 * getMeasuredWidth() 获得measure后的控件的实际宽度
                 */
                if (location[0] + mThumbOffset * 2 > x){
                    x = location[0] + mThumbOffset * 2;
                }else if (location[0] + mWidth - mThumbOffset * 2  < x){
                    x = location[0] + mWidth - mThumbOffset * 2;
                }
                mLocalX = x - mBubbleWidth/2;

                showBubble(mLocalX, mLocalY);
                break;

1.getRawX():获取手指在整个屏幕中的x坐标

int[] location = new int[2];
                this.getLocationInWindow(location);
                if (mBubbleAbove) {
                    mLocalY = location[1] - mHeight - mBubbleHeight + mBubbleOffsetPosition;
                }else {
                    mLocalY = location[1] + mBubbleOffsetPosition;
                }

getLocationInWindow 获取当前控件在整个屏幕中的位置;坐标指定点为左下角;(相当于,控件从左下角开始绘制)

mBubbleAbove: 设置bubble绘制在seekbar的上方还是下方;

mHeight - mBubbleHeight: mHeight:控件高度,mBubbleHeight:bubble的高度;

mLocalY = location[1] - mHeight - mBubbleHeight + mBubbleOffsetPosition; 作用是将bubble移动到seekbar的上方;

mBubbleOffsetPosition: 该属性可在布局中使用,设置bubble在Y轴上的偏移量;

if (location[0] + mThumbOffset * 2 > x){
                    x = location[0] + mThumbOffset * 2;
                }else if (location[0] + mWidth - mThumbOffset * 2  < x){
                    x = location[0] + mWidth - mThumbOffset * 2;
                }

用于判定bubble不越出范围;

  1. showBubble(mLocalX, mLocalY);
private void showBubble(float localX, float localY){

        mLayoutParams.x = (int) localX;
        mLayoutParams.y = (int) localY;
        if (null != mTvBubble) {
            mTvBubble.setText(String.valueOf(this.getProgress()));
        }
        mWindowManager.updateViewLayout(mViewBubble, mLayoutParams);
    }

更新View;

使用方法:

可直接在布局中直接使用;

提供以下自定义属性:

        <attr name="bubble" format="reference"/>
        <attr name="bubble_text_view" format="integer"/>
        <attr name="bubble_offset_position" format="dimension"/>
        <attr name="bubble_is_above" format="boolean"/>

bubble:指定bubble的布局文件;

bubble_text_view:指定 bubble布局下的textView的id;(已弃用,该为通过函数传入)

bubble_offset_position:指定bubble的相对偏移量;

bubble_is_above:设置bubble是在上方还是下方;

提供两个方法:

public void setBubble(ViewGroup bubble){
        mViewBubble = (ViewGroup) bubble;
    }



    public void setBubbleTextView(int bubbleTextViewId){
        if (null != mViewBubble) {
            mTvBubble = (TextView) mViewBubble.findViewById(bubbleTextViewId);
        }
    }

setBubble: 设置bubble的布局文件;

setBubbleTextView: 设置bubble布局下的textview控件,用于显示数据;若不指定,则默认为null;

存在问题:

TextView 和bubble的布局文件需要关联在一起;

但是这样做的好处就是,简化了textview在bubble中的属性设置