Android 仿QQ空间滑动改变图片

本文介绍如何在Android中创建自定义ShadeImageView组件,该组件可根据滚动位置动态调整图片遮罩效果,并实现点击事件区分。文章详细展示了ShadeImageView类的实现过程,包括图片缩放、坐标计算、触摸事件处理等关键技术点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建一个Android Library

写一个ShadeImageView类

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;
    }
}

让Library与Module进行关联

在Module中
Activity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.iv2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(((ShadeImageView)v).getWhere_click()== ShadeImageView.CLICK_SPACE)
                    Toast.makeText(MainActivity.this,"背景点击",Toast.LENGTH_SHORT).show();
                else if(((ShadeImageView)v).getWhere_click()== ShadeImageView.CLICK_RANGE)
                {
                    Toast.makeText(MainActivity.this,"范围点击",Toast.LENGTH_SHORT).show();
                }
            }
        });

    }
}

XML布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            >
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="360dp"
                android:orientation="vertical"
                >

            </LinearLayout>
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="240dp">
                <!--背景广告放在前面-->
                <ImageView
                    android:id="@+id/iv1"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="centerCrop"
                    android:src="@drawable/test2"
                    />
                <movie.bw.com.shadeview.ShadeImageView
                    android:id="@+id/iv2"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:src="@drawable/test"
                    />

            </RelativeLayout>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="480dp"
                android:orientation="vertical"
                >

            </LinearLayout>
            
        </LinearLayout>
    </ScrollView>
    
</RelativeLayout>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值