Android 自定义ImageView实现圆角/圆形 附加OnTouchListener详细注释以及Button圆角

转载请注明出处:王亟亟的大牛之路

平时要用一些非方方正正的按钮之类的小伙伴们是如何实现的?RadioButton?ImageButton?还是其他?
今天亟亟上的是ImageView来实现的
先上下效果图(目录结构)
Android 自定义ImageView实现圆角/圆形 附加OnTouchListener详细注释以及Button圆角_第1张图片

分析:

shape.xml用于Button的”倒角”(做过机械类的都懂,哈哈)
attr.xml用于自定义ImageView的标签的定义
ids.xml用于控件findbyid用,为什么补+id 等会我会来解释

效果图:

Android 自定义ImageView实现圆角/圆形 附加OnTouchListener详细注释以及Button圆角_第2张图片
分析:一个Button 2个自定义ImageView然后 这 3个东西都可以拖拽啊,点击啊等操作,我们来分析下代码。

先从圆角Button开始:

 <Button
        android:id="@+id/touch_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="快来按我" 
        android:background="@drawable/shape"/>

没什么特别的区别,只是因为@drawable/shape使得他的样式产生了变化
shape.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <!-- 填充的颜色 -->
    <solid android:color="#00FF00" />
    <!-- 设置按钮的四个角为弧形 -->
    <!-- android:radius 弧形的半径 -->
    <corners android:radius="25dip" />

<!-- padding:Button里面的文字与Button边界的间隔 -->
<padding  android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />
</shape>

Button就简单的实现了

CycleImageView

public class CycleImageView extends ImageView {

    private Paint mPaint;
    private Xfermode mXfermode = new PorterDuffXfermode(Mode.DST_IN);
    private Bitmap mMaskBitmap;

    private WeakReference<Bitmap> mWeakBitmap;

    /** * 图片的类型,圆形or圆角 */
    private int type;
    public static final int TYPE_CIRCLE = 0;
    public static final int TYPE_ROUND = 1;
    /** * 圆角大小的默认值 */
    private static final int BODER_RADIUS_DEFAULT = 10;
    /** * 圆角的大小 */
    private int mBorderRadius;

    public CycleImageView(Context context)
    {
        this(context,null);
        this.setOnTouchListener(new CustomOnTouchOnGesture());
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    public CycleImageView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        this.setOnTouchListener(new CustomOnTouchOnGesture());
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.CycleImageView);

        mBorderRadius = a.getDimensionPixelSize(
                R.styleable.CycleImageView_borderRadius, (int) TypedValue
                        .applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                                BODER_RADIUS_DEFAULT, getResources()
                                        .getDisplayMetrics()));// 默认为10dp
        Log.e("TAG", mBorderRadius+"");
        type = a.getInt(R.styleable.CycleImageView_type, TYPE_CIRCLE);// 默认为Circle

        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /** * 如果类型是圆形,则强制改变view的宽高一致,以小值为准 */
        if (type == TYPE_CIRCLE)
        {
            int width = Math.min(getMeasuredWidth(), getMeasuredHeight());
            setMeasuredDimension(width, width);
        }

    }

    //清缓存
    @Override
    public void invalidate()
    {
        mWeakBitmap = null;
        if (mMaskBitmap != null)
        {
            mMaskBitmap.recycle();
            mMaskBitmap = null;
        }
        super.invalidate();
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas)
    {
        //在缓存中取出bitmap
        Bitmap bitmap = mWeakBitmap == null ? null : mWeakBitmap.get();

        if (null == bitmap || bitmap.isRecycled())
        {
            //拿到Drawable
            Drawable drawable = getDrawable();
            //获取drawable的宽和高
            int dWidth = drawable.getIntrinsicWidth();
            int dHeight = drawable.getIntrinsicHeight();

            if (drawable != null)
            {
                //创建bitmap
                bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
                        Config.ARGB_8888);
                float scale = 1.0f;
                //创建画布
                Canvas drawCanvas = new Canvas(bitmap);
                //按照bitmap的宽高,以及view的宽高,计算缩放比例;因为设置的src宽高比例可能和imageview的宽高比例不同,这里我们不希望图片失真;
                if (type == TYPE_ROUND)
                {
                    // 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;
                    scale = Math.max(getWidth() * 1.0f / dWidth, getHeight()
                            * 1.0f / dHeight);
                } else
                {
                    scale = getWidth() * 1.0F / Math.min(dWidth, dHeight);
                }
                //根据缩放比例,设置bounds,相当于缩放图片了
                drawable.setBounds(0, 0, (int) (scale * dWidth),
                        (int) (scale * dHeight));
                drawable.draw(drawCanvas);
                if (mMaskBitmap == null || mMaskBitmap.isRecycled())
                {
                    mMaskBitmap = getBitmap();
                }
                // Draw Bitmap.
                mPaint.reset();
                mPaint.setFilterBitmap(false);
                mPaint.setXfermode(mXfermode);
                //绘制形状
                drawCanvas.drawBitmap(mMaskBitmap, 0, 0, mPaint);
                mPaint.setXfermode(null);
                //将准备好的bitmap绘制出来
                canvas.drawBitmap(bitmap, 0, 0, null);
                //bitmap缓存起来,避免每次调用onDraw,分配内存
                mWeakBitmap = new WeakReference<Bitmap>(bitmap);
            }
        }
        //如果bitmap还存在,则直接绘制即可
        if (bitmap != null)
        {
            mPaint.setXfermode(null);
            canvas.drawBitmap(bitmap, 0.0f, 0.0f, mPaint);
            return;
        }

    }
    /** * 绘制形状 * @return */
    public Bitmap getBitmap()
    {
        Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);

        if (type == TYPE_ROUND)
        {
            canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()),
                    mBorderRadius, mBorderRadius, paint);
        } else
        {
            canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2,
                    paint);
        }

        return bitmap;
    }

    class CustomOnTouchOnGesture implements OnTouchListener, OnGestureListener {

        GestureDetector myGesture = new GestureDetector(getContext(),this);
        View view = null;
        int[] temp = new int[] { 0, 0 };

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            //这一步只是我的强迫症而已,因为onTouch事件是不断被调用的
            if(view == null)
                view = v;
            myGesture.onTouchEvent(event);
            if(event.getAction()==MotionEvent.ACTION_UP){
                Log.d("onTouch", "getRawX: "+event.getRawX()+" getRawY: "+event.getRawY());
            }
            return true;
        }

        //在按下时调用 
        @Override
        public boolean onDown(MotionEvent e) {

            temp[0] = (int) e.getX();
            temp[1] = ((int) e.getRawY()) - view.getTop();
            return false;
        }

        //手指在触摸屏上迅速移动,并松开的动作。
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                float velocityY) {

            return false;
        }

        //长按的时候调用
        @Override
        public void onLongPress(MotionEvent e) {
            Toast.makeText(getContext(), "你长按了麦麦", Toast.LENGTH_LONG).show();

        }

        //按住然后滑动时调用
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY) {
            int x = (int) e2.getRawX();
            int y = (int) e2.getRawY();
            //设置试图所处的位置
            view.layout(x - temp[0], y - temp[1], x + view.getWidth() - temp[0], y - temp[1] + view.getHeight());
            return false;
        }

        // 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发 
        // 注意和onDown()的区别,强调的是没有松开或者拖动的状态
        @Override
        public void onShowPress(MotionEvent e) {


        }

        // 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            Toast.makeText(getContext(), "你点击了按钮", Toast.LENGTH_LONG).show();
            return false;
        }
    }
}

主Activity

public class MainActivity extends Activity {
    private Button touchButton;
    CycleImageView maimaicircle;
    CycleImageView maimairound;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        touchButton = (Button) findViewById(R.id.touch_button);  
        maimaicircle=(CycleImageView)findViewById(R.id.maimaicircle);
        maimairound=(CycleImageView)findViewById(R.id.maimairound);
        maimairound.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false;
            }
        }); 

        touchButton.setOnTouchListener(new CustomOnTouchOnGesture());
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    class CustomOnTouch implements OnTouchListener{
        int[] temp = new int[] { 0, 0 };
        Boolean ismove = false;
        int downX = 0;
        int downY = 0;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int eventaction = event.getAction();

            int x = (int) event.getRawX();
            int y = (int) event.getRawY();

            switch (eventaction) {

            case MotionEvent.ACTION_DOWN: // touch down so check if the
                temp[0] = (int) event.getX();
                temp[1] = y - v.getTop();
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                ismove = false;
                break;

            case MotionEvent.ACTION_MOVE: // touch drag with the ball
                v.layout(x - temp[0], y - temp[1], x + v.getWidth() - temp[0], y - temp[1] + v.getHeight());


                if (Math.abs(downX - x) > 10 || Math.abs(downY - y) > 10)
                    ismove = true;
                break;
            case MotionEvent.ACTION_UP:
                if (!ismove)
                    Toast.makeText(MainActivity.this, "你点击了这个按钮!!!!!!!!!!!", Toast.LENGTH_LONG).show();
                    Log.d("MotionEvent.ACTION_UP", "getRawX"+event.getRawX()+" getRawY"+event.getRawY());
                break;
            }
            return false;
        }
    }

    /* * getRawX:触摸点相对于屏幕的坐标 getX: 触摸点相对于view的坐标 getTop: 按钮左上角相对于父view(LinerLayout)的y坐标 getLeft: 按钮左上角相对于父view(LinerLayout)的x坐标 * */
    class CustomOnTouchOnGesture implements OnTouchListener, OnGestureListener {

        GestureDetector myGesture = new GestureDetector(MainActivity.this,this);
        View view = null;
        int[] value = new int[] { 0, 0 };

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            //这一步只是我的强迫症而已,因为onTouch事件是不断被调用的
            if(view == null)
                view = v;
            myGesture.onTouchEvent(event);
            if(event.getAction()==MotionEvent.ACTION_UP){
                Log.d("onTouch", "getRawX: "+event.getRawX()+" getRawY: "+event.getRawY());
            }
            return false;
        }

        //在按下时调用 
        @Override
        public boolean onDown(MotionEvent e) {
            value[0] = (int) e.getX();
            value[1] = ((int) e.getRawY()) - view.getTop();
            return false;
        }

        //手指在触摸屏上迅速移动,并松开的动作。
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                float velocityY) {
            return false;
        }

        //长按的时候调用
        @Override
        public void onLongPress(MotionEvent e) {
            Toast.makeText(MainActivity.this, "你长按了按钮", Toast.LENGTH_LONG).show();
        }

        //按住然后滑动时调用
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY) {
            int x = (int) e2.getRawX();
            int y = (int) e2.getRawY();
            view.layout(x - value[0], y - value[1], x + view.getWidth() - value[0], y - value[1] + view.getHeight());
            return false;
        }

        // 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发 
        // 注意和onDown()的区别,强调的是没有松开或者拖动的状态
        @Override
        public void onShowPress(MotionEvent e) {
        }

        // 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            Toast.makeText(MainActivity.this,"麦麦的位置是"+getMaiMaiLocal(), Toast.LENGTH_LONG).show();
            return false;
        }

    }
    public String getMaiMaiLocal(){
        int[] location = new int[2];  
        maimaicircle.getLocationOnScreen(location); 
         int x = location[0];  
         int y = location[1];  
        return   "图片各个角Left:"+maimaicircle.getLeft()+"Right:"+maimaicircle.getRight()+"Top:"+maimaicircle.getTop()+"Bottom:"+maimaicircle.getBottom();  
    }
}

主XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:wjj="http://schemas.android.com/apk/res/com.wjj.ontouchdemo"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wjj.ontouchdemo.Activity.MainActivity" >
    <com.wjj.ontouchdemo.CustomView.CycleImageView 
         android:id="@id/maimairound"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:src="@drawable/icon3" 
       wjj:type="round"
       wjj:borderRadius="20dp">        
    </com.wjj.ontouchdemo.CustomView.CycleImageView>

      <Button
        android:id="@+id/touch_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="快来按我" 
        android:background="@drawable/shape"/>

      <com.wjj.ontouchdemo.CustomView.CycleImageView
          android:id="@id/maimaicircle"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_alignParentRight="true"
          android:layout_alignParentTop="true"
          android:src="@drawable/icon3"
          wjj:type="circle" />

</RelativeLayout>

分析:

在做的过程中发现在XML文件中配置好的+id却在Activity中无法获取到他的Id除非自己在R文件中自己添加,为了避免麻烦才有了ids.xml。
所有的触屏的那些操作已经写在了自定义的ImageView里了,所以没像Button一样在MainActivity中使用内部类来操作。
详细的OnTouchListener, OnGestureListener周期的注释已经写在上面了。
大体内容就是如此。
那么这样一个图能做什么?
发散思维:
1.类似于加速球那一类的操作都可以
2.应对于特殊的形状要求

源码地址:http://yunpan.cn/cdRPzseyfw4rr 访问密码 deef

创作的过程中有一部分代码参照了网上的例子,如有雷同,请见谅。

你可能感兴趣的:(android,imageview,控件)