【Android 内存优化】自定义组件长图组件 ( 长图滚动区域解码 | 手势识别 GestureDetector | 滑动计算类 Scroller | 代码示例 )

简介: 【Android 内存优化】自定义组件长图组件 ( 长图滚动区域解码 | 手势识别 GestureDetector | 滑动计算类 Scroller | 代码示例 )

文章目录

一、GestureDetector 创建与设置

二、GestureDetector 触摸事件传递

三、触摸滑动操作

四、惯性滑动操作

五、长图滑动组件代码示例

六、运行效果

七、源码及资源下载



官方文档 API : BitmapRegionDecoder



在【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 ) 博客中完成了图像的区域解码 , 并显示在界面中 ; 本篇博客中主要完成长图滑动功能 , 触摸滑动 , 惯性滑动 , 操作 ;






一、GestureDetector 创建与设置


1 . 自定义组件中设置手势识别类 :



① 手势监听器实现 : 自定义组件实现 GestureDetector.OnGestureListener 接口 , 并重写 onDown , onShowPress , onSingleTapUp , onScroll , onLongPress , onFling 五个方法 ;


② 触摸监听器 : 自定义组件实现 OnTouchListener 触摸监听器 , 并重写 onTouch 方法 ;


③ 创建手势识别对象 : 创建 GestureDetector 对象 , 传入本组件作为手势监听器 ;


mGestureDetector = new GestureDetector(context, this);

1

④ 为组件设置触摸监听器 : 为本自定义组件设置触摸监听器 ;


setOnTouchListener(this);




2 . 代码示例 :


/**
 * 长图展示自定义 View 组件
 *
 */
public class LongImageView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
    public static final String TAG = "LongImageView";
    /**
     * 手势识别
     */
    private GestureDetector mGestureDetector;
    /**
     * 滑动类
     */
    private Scroller mScroller;
    public LongImageView(Context context) {
        this(context, null, 0);
    }
    public LongImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public LongImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 手势识别
        mGestureDetector = new GestureDetector(context, this);
        // 设置触摸监听器
        setOnTouchListener(this);
        // 滑动辅助类
        mScroller = new Scroller(context);
    }
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LongImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
                         int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    @Override
    public void computeScroll() {
    }
    /*
        下面的方法是手势识别监听器实现的方法
     */
    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }
    @Override
    public void onShowPress(MotionEvent e) {
    }
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }
    /**
     * 手指滑动事件, 此时手指没有离开屏蔽
     *
     * 随着滚动 , 改变图片的解码区域 ;
     *
     * @param e1 滑动的起始按下事件 DOWN 事件
     * @param e2 当前事件 MOVE 事件
     * @param distanceX 水平方向移动距离
     * @param distanceY 垂直方向移动距离
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }
    @Override
    public void onLongPress(MotionEvent e) {
    }
    /**
     * 惯性滑动
     *
     * @param e1
     * @param e2
     * @param velocityX x 方向速度
     * @param velocityY y 方向速度
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }
    /*
        下面的方法是触摸监听器实现方法
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 将触摸事件交给手势处理
        return mGestureDetector.onTouchEvent(event);
    }
}







二、GestureDetector 触摸事件传递


1 . 触摸事件传递给 GestureDetector : 在 View.OnTouchListener 触摸监听器的 onTouch 触摸回调方法中 , 将触摸事件传递给 mGestureDetector 处理 ;


   @Override

public boolean onTouch(View v, MotionEvent event) {
        // 将触摸事件交给手势处理
        return mGestureDetector.onTouchEvent(event);
    }



2 . 传递按下后事件 : 在 GestureDetector.OnGestureListener 监听器中的 onDown 方法中 , 要将返回值设置成 false , 此时事件才能传递下去 ;


   @Override

 

public boolean onDown(MotionEvent e) {
        // 触摸按下 , 此处注意 , 如果想要接收后续事件 , 此时需要设置成 true 返回值
        return true;
    }






三、触摸滑动操作


1 . 触摸滑动操作 :



① onScroll 方法 : 触摸滑动主要在 GestureDetector.OnGestureListener 监听器中的 onScroll 方法中实现 , 该方法是触摸滑动事件 , 手指全程没有离开屏幕 ;


② 区域解码操作 : 调用 mRect.offset 方法 , 重新设置解码区域 , 该方法可以移动 x 轴 , y 轴的解码 ,


向上滑动分析 : 当向上滑动时 , 触摸坐标由大变小 , distanceY 小于 0 , 应的图片也向上滑动 , 解码区域的 top 和 bottom 减小 ;


向下滑动分析 : 当向下滑动时 , 触摸坐标由小变大 , distanceY 大于 0 , 对应的图片也向下滑动 , 解码区域的 top 和 bottom 增加 ;


③ 解码区域限制 : 解码的最底部不能超过图片高度 , 解码的最顶部不能小于 0 ; 分别针对这两种情况进行各种限制 ;


if(mRect.bottom >= mImageHeight){
            mRect.bottom = mImageHeight;
            mRect.top = (int) (mImageHeight - mViewHeight / mScale);
        }
        if(mRect.bottom <= 0){
            mRect.top = 0;
            mRect.bottom = (int) (mViewHeight / mScale);
        }


④ 目的完成 : 该方法的目的就是重新计算 Rect 图像解码区域 , 计算好之后 , 调用 invalidate 方法 , 最终会在 onDraw 方法中解码 Rect 区域图片 , 并显示到自定义组件中 ;




2 . 代码示例


 

/**
     * 手指滑动事件, 此时手指没有离开屏蔽
     *
     * 随着滚动 , 改变图片的解码区域 ;
     *
     * @param e1 滑动的起始按下事件 DOWN 事件
     * @param e2 当前事件 MOVE 事件
     * @param distanceX 水平方向移动距离
     * @param distanceY 垂直方向移动距离
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        /*
            重新设置解码区域 , 该方法可以移动 x 轴 , y 轴的解码
            当向上滑动时 , 触摸坐标由大变小 , distanceY 小于 0 ,
            对应的图片也向上滑动 , 解码区域的 top 和 bottom 减小 ;
            当向下滑动时 , 触摸坐标由小变大 , distanceY 大于 0 ,
            对应的图片也向下滑动 , 解码区域的 top 和 bottom 增加 ;
         */
        mRect.offset(0, (int) distanceY);
        /*
            高度都不能超出范围
         */
        if(mRect.bottom >= mImageHeight){
            mRect.bottom = mImageHeight;
            mRect.top = (int) (mImageHeight - mViewHeight / mScale);
        }
        if(mRect.bottom <= 0){
            mRect.top = 0;
            mRect.bottom = (int) (mViewHeight / mScale);
        }
        // 重新绘制组件
        invalidate();
        return false;
    }







四、惯性滑动操作


惯性滑动需要借助 Scroller 进行辅助计算 ;



1 . Scroller 创建 : 在自定义组件的构造函数中创建 Scroller 对象;


mScroller = new Scroller(context);



2 . 惯性滑动回调方法 : 当发生惯性滑动时 , 此时手指已经离开屏幕 , 会自动回调 GestureDetector.OnGestureListener 监听器的 onFling 方法 , 主要在这个方法中根据监听到的速度值 , 计算惯性滑动的量 ;



3 . 惯性滑动计算 : 调用 Scroller 的 fling 方法 , 进行计算 , 在某时刻可以调用 Scroller 对象的 getCurrY 获取当前滑动到了哪里 ;


 

/**
     * 惯性滑动
     *
     * @param e1
     * @param e2
     * @param velocityX x 方向速度
     * @param velocityY y 方向速度
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        /*
            使用 Scroller 辅助计算滑动距离
            这里使用 Scroller 计算 mRect 区域的 top 值
         */
        mScroller.fling(
                0, mRect.top,   // x , y 起始位置
                0, (int) -velocityY,    // x , y 速度
                0, 0,   // x 的最小值和最大值
                0, (int) (mImageHeight - mViewHeight / mScale));    // y 的最小值和最大值
        return false;
    }



4 . 设置惯性滑动区域 : 惯性滑动后 , View 组件的 computeScroll 方法会自动回调 , 在这里计算 区域解码的 Rect 区域 , 计算完成后重绘组件 ;

/**
     * View 组件方法 , 父容器请求子容器更新其 mScrollX 和 mScrollY 值
     */
    @Override
    public void computeScroll() {
        // 如果 Scroller 计算惯性滑动结束 , 就不再计算
        if(mScroller.isFinished()){
            return;
        }
        // 动画还在继续执行
        if(mScroller.computeScrollOffset()) {
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
            // 重新绘制组件
            invalidate();
        }
    }



五、长图滑动组件代码示例


package kim.hsl.lgl;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.io.IOException;
import java.io.InputStream;
/**
 * 长图展示自定义 View 组件
 *
 */
public class LongImageView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
    public static final String TAG = "LongImageView";
    /**
     * 矩形区域
     */
    private Rect mRect;
    /**
     * Bitmap 解码选项
     */
    private BitmapFactory.Options mOptions;
    /**
     * 图片宽度
     */
    private int mImageWidth;
    /**
     * 图片高度
     */
    private int mImageHeight;
    /**
     * 组件宽度
     */
    private int mViewWidth;
    /**
     * 组件高度
     */
    private int mViewHeight;
    /**
     * 图像区域解码器
     */
    private BitmapRegionDecoder mBitmapRegionDecoder;
    /**
     * 显示的 Bitmap 图像
     */
    private Bitmap mBitmap;
    /**
     * 图片解析的缩放因子
     */
    private float mScale;
    /**
     * 手势识别
     */
    private GestureDetector mGestureDetector;
    /**
     * 滑动类
     */
    private Scroller mScroller;
    /**
     * 代码中创建组件调用该方法
     * @param context View 组件运行的上下文对象 , 一般是 Activity ,
     *                可以通过该上下获取当前主题 , 资源等
     */
    public LongImageView(Context context) {
        this(context, null, 0);
    }
    /**
     * 布局文件中使用组件调用该方法 ;
     * 当 View 组件从 XML 布局文件中构造时 , 调用该方法
     * 提供的 AttributeSet 属性在 XML 文件中指定 ;
     * 该方法使用默认的风格 defStyleAttr = 0 ,
     * 该组件的属性设置只有 Context 中的主题和 XML 中的属性 ;
     *
     * @param context View 组件运行的上下文环境 ,
     *                通过该对象可以获取当前主题 , 资源等
     * @param attrs XML 布局文件中的 View 组件标签中的属性值
     */
    public LongImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    /**
     * 布局文件中加载组件 , 并提供一个主题属性风格 ;
     * View 组件使用该构造方法 , 从布局中加载时 , 允许使用一个特定风格 ;
     * 如 : 按钮类的构造函数会传入 defStyleAttr = R.attr.buttonStyle 风格作为参数 ;
     *
     * @param context View 组件运行的上下文环境 ,
     *                通过该对象可以获取当前主题 , 资源等
     * @param attrs XML 布局文件中的 View 组件标签中的属性值
     * @param defStyleAttr 默认的 Style 风格
     *                     当前的应用 Application 或 Activity 设置了风格主题后 , 才生效
     */
    public LongImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 解码区域
        mRect = new Rect();
        // 解码选项
        mOptions = new BitmapFactory.Options();
        // 手势识别
        mGestureDetector = new GestureDetector(context, this);
        // 设置触摸监听器
        setOnTouchListener(this);
        // 滑动辅助类
        mScroller = new Scroller(context);
    }
    /**
     * 布局文件中加载组件 , 并提供一个主题属性属性 , 或风格资源 ;
     * 该构造方法允许组件在加载时使用自己的风格 ;
     *
     * 属性设置优先级 ( 优先级从高到低 )
     * 1. 布局文件中的标签属性 AttributeSet
     * 2. defStyleAttr 指定的默认风格
     * 3. defStyleRes 指定的默认风格
     * 4. 主题的属性值
     *
     * @param context View 组件运行的上下文环境 ,
     *                通过该对象可以获取当前主题 , 资源等
     * @param attrs XML 布局文件中的 View 组件标签中的属性值
     * @param defStyleAttr 默认的 Style 风格
     *                     当前的应用 Application 或 Activity 设置了风格主题后 , 才生效
     * @param defStyleRes style 资源的 id 标识符 , 提供组件的默认值 ,
     *                    只有当 defStyleAttr 参数是 0 时 , 或者主题中没有 style 设置 ;
     *                    默认可以设置成 0 ;
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LongImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
                         int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    /**
     * 设置显示的图片
     * @param inputStream
     */
    public void setImage(InputStream inputStream){
        // 读取图片的尺寸数据
        mOptions.inJustDecodeBounds = true;
        // 解码图片 , 图片相关的尺寸数据保存到了 mOptions 选项中
        BitmapFactory.decodeStream(inputStream, null, mOptions);
        // 获取图片宽高
        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;
        // 设置 Bitmap 内存复用
        mOptions.inMutable = true;  // 设置可变
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565; // 设置像素格式 RGB 565
        mOptions.inJustDecodeBounds = false; // 读取完毕之后, 就需要解析实际的 Bitmap 图像数据了
        try {
            // Bitmap 区域解码器
            mBitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 设置图片完毕后 , 刷新自定义组件
        requestLayout();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取测量的自定义 View 组件宽高
        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();
        // 根据组件的宽高 , 确定要加载的图像的宽高
        if(mBitmapRegionDecoder != null){
            mRect.left = 0;
            mRect.top = 0;
            // 绘制的宽度就是图像的宽度
            mRect.right = mImageWidth;
            // 根据图像宽度 和 组件宽度 , 计算出缩放比例
            // 组件宽度 / 图像宽度 = 缩放因子
            mScale = (float)mViewWidth / (float)mImageWidth;
            /*
                加载的图像高度宽度 , 与组件的高度宽度比例一致
                mViewWidth / 加载的图像宽度 = mViewHeight / 加载的图像高度
                此处加载的图像宽度就是实际的宽度
                 加载的图像高度 = mViewHeight / ( mViewWidth / 加载的图像宽度 )
                 mViewWidth / 加载的图像宽度 就是缩放因子
                 加载的图像高度 = mViewHeight / 缩放因子
             */
            // 根据缩放因子计算解码高度
            mRect.bottom = (int) (mViewHeight / mScale);
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mBitmapRegionDecoder == null) return;
        // 内存复用
        mOptions.inBitmap = mBitmap;
        // 解码图片
        mBitmap = mBitmapRegionDecoder.decodeRegion(mRect, mOptions);
        // 设置绘制的图像缩放 , x 轴和 y 轴都在 Bitmap 大小的区域基础上 , 缩放 mScale 倍
        Matrix matrix = new Matrix();
        matrix.setScale(mScale, mScale);
        canvas.drawBitmap(mBitmap, matrix, null);
    }
    /**
     * View 组件方法 , 父容器请求子容器更新其 mScrollX 和 mScrollY 值
     */
    @Override
    public void computeScroll() {
        // 如果 Scroller 计算惯性滑动结束 , 就不再计算
        if(mScroller.isFinished()){
            return;
        }
        // 动画还在继续执行
        if(mScroller.computeScrollOffset()) {
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
            // 重新绘制组件
            invalidate();
        }
    }
    /*
        下面的方法是手势识别监听器实现的方法
     */
    @Override
    public boolean onDown(MotionEvent e) {
        // 触摸按下之后 , 就不能在滑动了 , 如果图片还在按之前的惯性滑动 , 此时需要强行终止滑动
        if(!mScroller.isFinished()){
            // 强制终止 Scroller 滑动
            mScroller.forceFinished(true);
        }
        // 触摸按下 , 此处注意 , 如果想要接收后续事件 , 此时需要设置成 true 返回值
        return true;
    }
    @Override
    public void onShowPress(MotionEvent e) {
    }
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }
    /**
     * 手指滑动事件, 此时手指没有离开屏蔽
     *
     * 随着滚动 , 改变图片的解码区域 ;
     *
     * @param e1 滑动的起始按下事件 DOWN 事件
     * @param e2 当前事件 MOVE 事件
     * @param distanceX 水平方向移动距离
     * @param distanceY 垂直方向移动距离
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        /*
            重新设置解码区域 , 该方法可以移动 x 轴 , y 轴的解码
            当向上滑动时 , 触摸坐标由大变小 , distanceY 小于 0 ,
            对应的图片也向上滑动 , 解码区域的 top 和 bottom 减小 ;
            当向下滑动时 , 触摸坐标由小变大 , distanceY 大于 0 ,
            对应的图片也向下滑动 , 解码区域的 top 和 bottom 增加 ;
         */
        mRect.offset(0, (int) distanceY);
        /*
            高度都不能超出范围
         */
        if(mRect.bottom >= mImageHeight){
            mRect.bottom = mImageHeight;
            mRect.top = (int) (mImageHeight - mViewHeight / mScale);
        }
        if(mRect.bottom <= 0){
            mRect.top = 0;
            mRect.bottom = (int) (mViewHeight / mScale);
        }
        // 重新绘制组件
        invalidate();
        return false;
    }
    @Override
    public void onLongPress(MotionEvent e) {
    }
    /**
     * 惯性滑动
     *
     * @param e1
     * @param e2
     * @param velocityX x 方向速度
     * @param velocityY y 方向速度
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        /*
            使用 Scroller 辅助计算滑动距离
            这里使用 Scroller 计算 mRect 区域的 top 值
         */
        mScroller.fling(
                0, mRect.top,   // x , y 起始位置
                0, (int) -velocityY,    // x , y 速度
                0, 0,   // x 的最小值和最大值
                0, (int) (mImageHeight - mViewHeight / mScale));    // y 的最小值和最大值
        return false;
    }
    /*
        下面的方法是触摸监听器实现方法
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 将触摸事件交给手势处理
        return mGestureDetector.onTouchEvent(event);
    }
}







六、运行效果


横屏长图滚动效果 :

image.png




竖屏长图滚动效果 :

image.png







七、源码及资源下载


源码及资源下载地址 :


① GitHub 工程地址 : Long_Graph_Loading


② LongImageView.java 主界面代码地址 : LongImageView.java , 这是上述示自定义组件代码 ;


目录
相关文章
|
4月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
531 3
|
8月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
572 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
8月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
163 0
|
8月前
|
Android开发 开发者
Android自定义view之利用drawArc方法实现动态效果
本文介绍了如何通过Android自定义View实现动态效果,重点使用`drawArc`方法完成圆弧动画。首先通过`onSizeChanged`进行测量,初始化画笔属性,设置圆弧相关参数。核心思路是不断改变圆弧扫过角度`sweepAngle`,并调用`invalidate()`刷新View以实现动态旋转效果。最后附上完整代码与效果图,帮助开发者快速理解并实践这一动画实现方式。
209 0
|
8月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
157 0
|
4月前
|
存储 大数据 Unix
Python生成器 vs 迭代器:从内存到代码的深度解析
在Python中,处理大数据或无限序列时,迭代器与生成器可避免内存溢出。迭代器通过`__iter__`和`__next__`手动实现,控制灵活;生成器用`yield`自动实现,代码简洁、内存高效。生成器适合大文件读取、惰性计算等场景,是性能优化的关键工具。
288 2
|
8月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
398 65
Android自定义view之网易云推荐歌单界面
|
8月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
755 84
|
8月前
|
Android开发 开发者
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
本文详细介绍了如何通过自定义 `attrs.xml` 文件实现 Android 自定义 View 的属性配置。以一个包含 TextView 和 ImageView 的 DemoView 为例,讲解了如何使用自定义属性动态改变文字内容和控制图片显示隐藏。同时,通过设置布尔值和点击事件,实现了图片状态的切换功能。代码中展示了如何在构造函数中解析自定义属性,并通过方法 `setSetting0n` 和 `setbackeguang` 实现功能逻辑的优化与封装。此示例帮助开发者更好地理解自定义 View 的开发流程与 attrs.xml 的实际应用。
245 2
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
|
8月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
252 3