Android自定义控件仿QQ空间广告效果

    有一天在逛QQ空间的时候,发现有一个广告效果觉得比较酷炫,即上下滑动的时候,两个图片广告以圆圈扩散的形式展现。闲来无事,便着手打造了一个,效果不是完全相同,但按照我这个思路是可以打造成完全相同的效果的。下面便是我的打造过程,希望能为有需要而没思路的朋友带来点点启发。

    大体思路是,继承ImageView,利用Paint类里的setXfermode图像混合方法进行动态绘制。具体代码如下:

    

package com.ct.shadeview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.ImageView;

/**
 * Created by ChenTao on 2018/4/17.
 */

public class ShadeImageView extends ImageView {

    private ViewTreeObserver viewTreeObserver = null;
    private ViewTreeObserver.OnScrollChangedListener mOnScrollChangedListener = null;
    private Bitmap bm = null;
    private Context context;
    private int screenHeight = 0;//屏幕高度

    private float scale = 1;//图片将要的缩放比例,用于画圆时中心点的实际位置

    private float oriYAbs = 0,oriXAbs = 0,oriRAbs = 0;//记录比例放大后的圆的圆点和半径,用于判断点击位置

    public final static int CLICK_SPACE = 0;//点击空白处
    public final static int CLICK_RANGE = 1;//点击在圆上
    private int where_click = 0;//用于返回点击在哪里

    public ShadeImageView(Context context) {
        super(context);
        this.context = context;
        this.setScaleType(ScaleType.CENTER_CROP);
    }

    public ShadeImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        this.setScaleType(ScaleType.CENTER_CROP);
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        // TODO Auto-generated method stub
        super.setImageBitmap(bm);//会调用setImageDrawable函数
    }
    @Override
    public void setImageResource(int resId) {
        // TODO Auto-generated method stub
        Drawable drawable = ContextCompat.getDrawable(context,resId);
        setImageDrawable(drawable);//会调用setImageDrawable函数
    }
    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Drawable drawable = getDrawable();
        if(drawable!=null)
        {
            bm = drawableToBitmap(drawable);//获取设置的图片
        }
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        screenHeight = dm.heightPixels;

        viewTreeObserver = getViewTreeObserver();
        mOnScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
               drawLocation();
            }
        };
        viewTreeObserver.addOnScrollChangedListener(mOnScrollChangedListener);
        //view加载完成调用
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                // TODO Auto-generated method stub
                drawLocation();//画出初始的时候样子
            }
        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(bm!=null)
         scale = getScale(bm,getMeasuredWidth(),getMeasuredHeight());

    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                float x = e.getX();
                float y = e.getY();
                where_click = Math.sqrt((x-oriXAbs)*(x-oriXAbs)+(y-oriYAbs)*(y-oriYAbs))>oriRAbs?0:1;//确定点击范围
                break;
        }
        return super.onTouchEvent(e);//继续往下传
    }

    @Override
    protected void onDetachedFromWindow() {

        viewTreeObserver.removeOnScrollChangedListener(mOnScrollChangedListener);
        super.onDetachedFromWindow();
    }

    public int getWhere_click() {
        return where_click;
    }

    private void drawLocation()//根据位置进行画图
    {
        if(bm!=null)
        {
            int[] location = new int[2];
            getLocationOnScreen(location);//获取当前view左上点在屏幕中的坐标
            float locationY = (float) location[1];
            //oriXAbs ,oriYAbs为图片按屏幕比例放大后的抽象原点,实际原点需要比例缩放
            oriXAbs = (bm.getWidth()*scale-getMeasuredWidth())/2+getMeasuredHeight()/4;//由于绘制的时候是根据画布来进行坐标计算,
            // 所以需要计算圆的原点。
            oriYAbs = (bm.getHeight()*scale-getMeasuredHeight())/2+getMeasuredHeight()/4;
            //oriX,oriY实际在画布里的原点
            int oriX = (int) (oriXAbs/scale);
            int oriY = (int) (oriYAbs/scale);

            //计算半径
            //中心位置
            float centerY = screenHeight/2;
            float startY = screenHeight/2-getMeasuredHeight();
            float startY2 = screenHeight/2+getMeasuredHeight();
            //计算当控件滑到中心以下时,圆全部覆盖后的半径。
            float endX = getMeasuredHeight()*3/4;
            float endY = getMeasuredWidth() - getMeasuredHeight()/4;
            float endR = (float) Math.sqrt(endX*endX+endY*endY)/scale;
            if(locationY<centerY && locationY>startY) {
                //开始绘画位置
                int r = (int) ((locationY-startY)*endR/(centerY-startY));
                oriRAbs = r*scale;//记录控件你显示的半径范围,用于实际点击
                Bitmap shadeBm = getShadeBitmap(bm, r, oriX, oriY);
                setImageBitmap(shadeBm);
            }
            else if(locationY<=startY)
            {
                Bitmap shadeBm = getShadeBitmap(bm, 0, oriX, oriY);
                oriRAbs = 0;//记录控件你显示的半径范围,用于实际点击
                setImageBitmap(shadeBm);
            }
            else
            {
                Bitmap shadeBm = getShadeBitmap(bm, (int) endR, oriX, oriY);
                oriRAbs = endR*scale;//记录控件你显示的半径范围,用于实际点击
                setImageBitmap(shadeBm);
            }
        }
    }

    public Bitmap getShadeBitmap(Bitmap bitmap, int r, int oriX, int oriY) {//获取遮罩图片,r为遮罩圆的半径,上滑时,oriX为原点的xy值,这是相对于控件的

        Paint paint = new Paint(); //创建笔
        paint.setAntiAlias(true);//给Paint加上抗锯齿标志

        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(output); //创建一个画布,以画布的坐标为标准
        Rect rect = new Rect(oriX-r, oriY-r, r+oriX, r+oriY); //构造一个矩形,前面两个参数代表的是左上点
        RectF rectF = new RectF(rect);
        canvas.drawRoundRect(rectF, r, r, paint);
        canvas.save();

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));//取两层绘制交集。显示上层。遮罩,先画的在上面,后画的在下面
        //要先画圆再设置

        Rect rect2 = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); //构造一个矩形
        canvas.drawBitmap(bitmap, rect2, rect2, paint);//第一个Rect 代表要绘制的bitmap 区域,第二个 Rect 代表的是要将bitmap 绘制在画布的什么地方,绘制的时候不会改变图片本身

        return output;
    }

    /**
     * Drawable转化为Bitmap
     */
    private Bitmap drawableToBitmap(Drawable drawable) {
        int width = drawable.getIntrinsicWidth();//获得图片实际宽度
        int height = drawable.getIntrinsicHeight();//获得图片实际高度
        Bitmap bitmap = Bitmap.createBitmap(width, height, drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);//创建bitmap,用于然后创建它的画布
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, width, height);
        drawable.draw(canvas);
        return bitmap;
    }

    private float getScale(Bitmap bitmap,float vw,float vh)//获得,图片centerCrop时将获得的缩放比例
    {
        float width = bitmap.getWidth();
        float height = bitmap.getHeight();
        float scale = 1;
        scale = vw/width>vh/height?vw/width:vh/height;
        return scale;
    }
}

    注意代码中的getScale方法,这是因为我设置了图片的显示模式就是ScaleType.CENTER_CROP,所以图片在显示的时候是会按照view的大小进行等比例缩放的,getScale就是用来获取这个比例的。这个比例用于,当Canvas画图时是根据原图绘制,而画圆心时是根据控件大小来确定位置,所以要根据比例计算出在原图的位置。具体可以详见代码,代码较为简单应该都可以看懂。最终效果如下:

源码地址:点击打开链接希望有用到的童鞋star一下哦,蟹蟹

猜你喜欢

转载自blog.csdn.net/chen12521252/article/details/80688388