如何应对Android面试官-> 自定义 PhotoView 事件分发

RK3572-Android16-记录一次OTA升级失败解决方案 这里记录一次解决RK3576 Android16 OTA 用Recovery 升级失败的一次解决案例,将部分经验作为总结。本次OTA升级耗时了几天,最终是RK给出的解决方案,再加上经验来解决了其它部分问题新平台,高一点的Android版本,可能本身有bug,多请教平台来,不要蒙着干,干不出来的。 阅读详情

前言

infoflow 2024-02-02 14-30-47.png

先上效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现的是双指放大和缩小、双击放大和缩小,以及放大之后支持拖动的一个效果;

基础架子


我们先来搭建一个简单的架子

public class PhotoView extends View {

    private static final float IMAGE_WIDTH = Utils.dpToPixel(300);
    private Bitmap bitmap;
    private Paint paint;


    public PhotoView(Context context) {
        this(context, null);
    }

    public PhotoView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PhotoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    private void init() {
        bitmap = Utils.getPhoto(getResources(), (int) IMAGE_WIDTH);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(bitmap, 0, 0, paint);
    }
}

我们运行看下效果:

infoflow 2024-02-02 16-24-59.png

接下来我们开始一步一步实现最终的效果

绘制图片到屏幕中间

想要将图片绘制到屏幕中间,也就是 canvas.drawBitmap 的时候,坐标起始点不是 0,0 了,而是 屏幕宽度的一半 减去 图片宽度的一半,屏幕高度的一半 减去 图片高度的一半;

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    originalOffsetX = (float) getWidth() / 2f - (float) bitmap.getWidth() / 2f;
    originalOffsetY = (float) getHeight() / 2f - (float) bitmap.getHeight() / 2f;
}

我们运行看下效果:

Screenshot_20240202_163120.png

可以看到,图片绘制到了屏幕中心位置,接下来我们实现缩放能力

缩放和比例计算

我们再展示图片的时候,其实是需要一个缩放的,也就是说需要让我们的图片的宽或者高可以适配屏幕,所以是分为两种情况

一种是 宽度充满

infoflow 2024-02-02 16-39-22.png

一种是 高度充满

infoflow 2024-02-02 16-43-13.png

我们来给这两种充满分别定义一个概念,宽度充满我们称为『小放大 smallScale』,高度充满我们称为『大放大 bigScale』,我们需要计算使用哪种缩放方式

缩放计算方式(smallScale)


protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    if ((float) bitmap.getWidth() / bitmap.getHeight() > (float) getWidth() / getHeight()){
        smallScale = (float) getWidth() / bitmap.getWidth();
    }
    currentScale = smallScale;
}

也就是假设下面的 这种计算方式,图片的宽/高 = 2,屏幕的宽/高 = 0.5,所以是宽度充满的缩放比例

infoflow 2024-02-02 16-50-03.png

缩放计算方式(bigScale)


反之,就是大缩放

protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    if ((float) bitmap.getWidth() / bitmap.getHeight() > (float) getWidth() / getHeight()){
        smallScale = (float) getWidth() / bitmap.getWidth();
        bigScale = (float) getHeight() / bitmap.getHeight();
    }
    currentScale = smallScale;
}

然后我们需要在绘制之前,设置一下 Canvas 的缩放,通过 canvas.scale(); 来设置,我们可以看下这个 scale 方法

infoflow 2024-02-02 16-56-54.png

sx 和 sy 这里就不做过多解释了,这个 px 和 py 指的是 scale 的中心点坐标,是从 0,0 点缩放,还是指定的位置缩放;

如果我们想让图片以中心点缩放,而我们的图片现在又是绘制在屏幕中间,所以缩放的中心点 其实就是屏幕的中心点;

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.scale(currentScale, currentScale, (float) getWidth() / 2f, (float) getHeight() / 2f);
}

另外,图片放大之后,会进行一个平移来查看完整的图片,所以我们需要给图片进行一个边界的设置,例如:当我们的图片高度充满屏幕的时候,我们这个时候可以认为垂直方向是不可以移动的,只能水平方向左右移动,为了避免这种情况,bigScale 需要配置一个系数,让垂直方向也可以移动;

private float OVER_SCALE_FACTOR = 1.5f;
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    // 
    if ((float) bitmap.getWidth() / bitmap.getHeight() > (float) getWidth() / getHeight()) {
        smallScale = (float) getWidth() / bitmap.getWidth();
        bigScale = (float) getHeight() / bitmap.getHeight() * OVER_SCALE_FACTOR;
    } else {
        smallScale = (float) getHeight() / bitmap.getHeight();
        bigScale = (float) getWidth() / bitmap.getWidth() * OVER_SCALE_FACTOR;
    }

    currentScale = smallScale;
}

手势处理

我们接下来进行手势的处理,也就是双击放大、缩小,放大之后可以拖动,以及原始状态双指缩放;

系统为我们提供了 GestureDetector(手势探测器)我们可以直接使用这个 API,它就会为我们来处理单击、双击等手势,我们来使用它完成手势的处理;

private ScaleGestureDetector scaleGestureDetector;

private void init(Context context) {
    //
    ...
    // 
    gestureDetector = new GestureDetector(context, new PhotoGestureDetector());
}

// 为了便于理解,重写了 SimpleOnGestureListener 的所有方法

private static class PhotoGestureDetector extends GestureDetector.SimpleOnGestureListener {

    public PhotoGestureDetector() {
        super();
    }

    /**
     * up的时候触发,非长按单击或者双击的第一次点击触发
     * @param e The up motion event that completed the first tap 
     * @return true
     */
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return super.onSingleTapUp(e);
    }

    /**
     * 长按事件处理
     * @param e The initial on down motion event that started the longpress. 
     */
    @Override
    public void onLongPress(MotionEvent e) {
        super.onLongPress(e);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return super.onScroll(e1, e2, distanceX, distanceY);
    }

    /**
     * up 手指抬起之后的惯性活动 -- 大于 50dp/s
     *
     * @param e1        The first down motion event that started the fling.
     * @param e2        The move motion event that triggered the current onFling.
     * @param velocityX The velocity of this fling measured in pixels per second
     *                  along the x axis.
     * @param velocityY The velocity of this fling measured in pixels per second
     *                  along the y axis.
     * @return true
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return super.onFling(e1, e2, velocityX, velocityY);
    }

    /**
     * 延迟 100ms 触发,用来处理点击效果
     * @param e The down motion event 
     */
    @Override
    public void onShowPress(MotionEvent e) {
        super.onShowPress(e);
    }

 
    /**
     * 只要想 响应事件,这里就需要返回 true
     * @param e The down motion event. 
     * @return true
     */
    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    /**
     * 双击的第二次down触发,双击触发时间 需要 40ms- 300ms,小于40认为是抖动,超过300认为是单击
     *
     * @param e The down motion event of the first tap of the double-tap.
     * @return true
     */
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        return super.onDoubleTap(e);
    }

    /**
     * 双击的第二次down、move、up都触发
     * @param e The motion event that occurred during the double-tap gesture.
     * @return true
     */
    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        return super.onDoubleTapEvent(e);
    }
    
    /**
     * 单击按下时触发,双击不触发,down、up时都可能触发,但是不会同时触发
     * @param e The down motion event of the single-tap.
     * @return true
     */
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        return super.onSingleTapConfirmed(e);
    }

    @Override
    public boolean onContextClick(MotionEvent e) {
        return super.onContextClick(e);
    }
}

双击放大处理


前面我们已经分析了各个方法的作用,双击的处理,是在 onDoubleTap 中处理,我们需要一个标志位来判断,当次需要小缩放还是大缩放(双击放大 -> 双击缩小)

priviate boolean isEnLarge = false;

public boolean onDoubleTap(MotionEvent e) {
    isEnLarge = !isEnLarge;
    if (isEnLarge) {
        currentScale = bigScale;
    } else {
        currentScale = smallScale;
    }
    // 这个方法调用之后,会重新走 onDraw 逻辑,也就会还行 canvas.scale 逻辑
    invalidate();
    return super.onDoubleTap(e);
}

我们运行看下效果:

SVID_20240204_111136_1.gif

可以看到,双击并没有效果,这是为什么呢?

原因就是,我们的 GestureDetector 需要接管我们 View 的 onTouchEvent 事件才能响应,它本身就是对 Touch 事件的处理;

@Override
public boolean onTouchEvent(MotionEvent event) {
    return gestureDetector.onTouchEvent(event);
}

我们再来看下效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,实现了我们期望的双击放大,双击缩小的效果;

但是整体效果是瞬间变大,瞬间变小,缩放的效果不是很好,我们可以加一个缩放动画,来让它有一个变化的过程,我们使用缩放属性动画来搞一下;

private ObjectAnimator scaleAnimator;

private ObjectAnimator getScaleAnimator() {
    if (scaleAnimator == null) {
        scaleAnimator = ObjectAnimator.ofFloat(this, "currentScale", 0);
    }
    scaleAnimator.setFloatValues(smallScale, bigScale);
    return scaleAnimator;
}

public float getCurrentScale() {
    return currentScale;
}

public void setCurrentScale(float currentScale) {
    this.currentScale = currentScale;
    invalidate();
}

然后我们的双击回调中,启动这个动画即可

@Override
public boolean onDoubleTap(MotionEvent e) {
    isEnLarge = !isEnLarge;
    if (isEnLarge) {
        // 放大的时候,启动缩放动画
        getScaleAnimator().start();
    } else {
        // 缩小的时候,反转这个动画
        getScaleAnimator().reverse();
    }
    return super.onDoubleTap(e);
}

我们运行看下效果:

SVID_20240204_112403_1.gif

可以看到有一个平滑的缩放效果了;

手指的滑动


我们接下来处理下放大之后的手指滑动,图片跟着滑动,其实就是 canvas 的 translate 手指滑动的距离;

private float offsetX;
private float offsetY;

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(offsetX, offsetY);
}

然后我们只需要赋值这个 offsetX 和 offsetY 即可,我们需要在 onScroll 中进行滑动事件的处理,也就是 offsetX 和 offsetY 的处理;

/**
 * @param e1        手指按下位置.
 * @param e2        当前位置.
 * @param distanceX 旧位置 - 新位置.
 * @param distanceY 旧位置 - 新位置.
 * @return true
 */
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    // 放大才能滑动
    if (isEnLarge) {
        offsetX = offsetX - distanceX;
        offsetY = offsetY - distanceY;
        invalidate();
    }
    return super.onScroll(e1, e2, distanceX, distanceY);
}

我们运行看下效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们实现了滑动效果

设置滚动边界


可以看到上面的效果,当滑动到边界的时候,还是可以继续滑动,我们来处理下这个问题,当滑动到边界的时候,不再支持滑动

infoflow 2024-02-04 12-19-50.png

黑色是屏幕,红色是放大之后的默认位置,绿色表示我们在拖动,而 offsetY 的值,就是图片和屏幕之间的那个距离,同理 offsetX 就是左边距和右边距的值,这里要考虑坐标轴上的:『下正上负,右正左负』

即:(图片的高度 - 屏幕的高度)/ 2 就是 offsetY 可以移动的距离;
所以,边界设定如下:

private void fixOffset() {
        offsetX = Math.min(offsetX, ((bitmap.getWidth() * bigScale - getWidth()) / 2));
        offsetX = Math.max(offsetX, -((bitmap.getWidth() * bigScale - getWidth()) / 2));
        offsetY = Math.min(offsetY, ((bitmap.getHeight() * bigScale - getHeight()) / 2));
        offsetY = Math.max(offsetY, -((bitmap.getHeight() * bigScale - getHeight()) / 2));
}

然后在 scroll 方法中调用这个方法,然后我们运行看下效果:

/**
 * @param e1        手指按下位置.
 * @param e2        当前位置.
 * @param distanceX 旧位置 - 新位置.
 * @param distanceY 旧位置 - 新位置.
 * @return true
 */
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    // 放大才能滑动
    if (isEnLarge) {
        offsetX = offsetX - distanceX;
        offsetY = offsetY - distanceY;
        fixOffset();
        invalidate();
    }
    return super.onScroll(e1, e2, distanceX, distanceY);
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,滑动到边界之后就不能再滑动了,达到了我们期望的效果;

设置惯性滑动


上图可以看到,我们还没有处理惯性滑动的效果,我们来处理下,前面有介绍 onFling 是处理惯性滑动的,我们在这个方法中借用 OverScroll 这个 API 来实现我们的惯性滑动;

惯性滑动,也是依赖要在放大状态下处理:

private OverScroller overScroller;

private void init(Context context) {
    overScroller = new OverScroller(context);
}

/**
 * up 手指抬起之后的惯性活动
 *
 * @param e1        The first down motion event that started the fling.
 * @param e2        The move motion event that triggered the current onFling.
 * @param velocityX x 轴方向运动速度,px/s.
 * @param velocityY y 轴方向运动速度,px/s.
 * @return true
 */
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    if (isEnLarge) {
        // 使用 fling 实现惯性滑动
        overScroller.fling((int) offsetX, (int) offsetY, (int) velocityX, (int) velocityY,
                (int) (-(bitmap.getWidth() * bigScale - getWidth())/2),
                (int) ((bitmap.getWidth() * bigScale - getWidth())/2),
                (int) (-(bitmap.getHeight() * bigScale - getHeight())/2),
                (int) ((bitmap.getHeight() * bigScale - getHeight())/2),
                400, 400);
        // 下一帧动画的时候执行
        postOnAnimation(new Runnable() {
            @Override
            public void run() {
                if (overScroller.computeScrollOffset()) {
                    offsetX = overScroller.getCurrX();
                    offsetY = overScroller.getCurrY();
                    invalidate();
                    postOnAnimation(this);
                }
            }
        });
        
    }
    return super.onFling(e1, e2, velocityX, velocityY);
}

我们运行看下效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,我们的惯性滑动,以及回弹效果都实现了;

双指缩放


可以看到,我们现在还没有实现双指缩放的能力,我们来搞一下,这里我们借助 ScaleGestureDetector 这个系统的提供的 API 来实现;

private ScaleGestureDetector scaleGestureDetector;

private void init(Context context) {
    
    scaleGestureDetector = new ScaleGestureDetector(context, new PhotoScaleGestureListener);
}

private class PhotoScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        return false;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        // 这里一定要返回 true,否则不生效
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {

    }
}

我们需要在这个 onScale 和 onScaleBegin 方法中处理我们的缩放系数,然后进行重绘;

float initScale;

@Override
public boolean onScale(ScaleGestureDetector detector) {
    // 缩放因子
    currentScale = initScale * detector.getScaleFactor();
    invalidate();
    return false;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
    initScale = currentScale;
    return true;
}

同样的,只要是手势探测,我们都需要接管 onTouchEvent 事件,双指缩放的优先级高于手指滑动的优先级;

@Override
public boolean onTouchEvent(MotionEvent event) {
    // 优先双指缩放的手势处理
    boolean result = scaleGestureDetector.onTouchEvent(event);
    // 如果双指缩放不处理了,在交给滑动手势处理
    if (!scaleGestureDetector.isInProgress()) {
        result = gestureDetector.onTouchEvent(event);
    }
    return result;
}

我们运行看下效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,我们实现了双指缩放的效果;但是有一些不足之处,例如:平移后缩小,直接留白了很多,我们来处理下;

实际上我们的平移(canvas.translate)和缩放(canvas.scale)是可以进行绑定的,我们在缩放的时候是有一个缩放系数的,那么我们在平移的时候也可以和这个系数关联起来;

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 
    float scaleFaction = (currentScale - smallScale) / (bigScale - smallScale);
    canvas.translate(offsetX * scaleFaction, offsetY * scaleFaction);
    canvas.scale(currentScale, currentScale, (float) getWidth() / 2, (float) getHeight() / 2);
}

我们运行看下效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,缩放的时候,并没有了留白的badcase;并且缩小到宽度充满屏幕之后,就不再响应缩放的手势了;

双击偏移


可以看到,我们再次双击放大的时候,并没有从点击位置开始缩放,我们需要在双击的时候修改下偏移量,也就是我们缩小之后,再次双击放大的时候,应该是从哪里点击,就以哪里为中心进行放大处理;

/**
 * 双击触发时间 需要 40ms- 300ms 之间来处理
 *
 * @param e The down motion event of the first tap of the double-tap.
 * @return true
 */
@Override
public boolean onDoubleTap(MotionEvent e) {
    isEnLarge = !isEnLarge;
    if (isEnLarge) {
        offsetX = (e.getX() - getWidth() / 2f) -
                (e.getX() - getWidth() / 2f) * bigScale / smallScale;
        offsetY = (e.getY() - getHeight() / 2f) -
                (e.getY() - getHeight() / 2f) * bigScale / smallScale;
        // 归正偏移量的值就可以        
        fixOffset();        
        getScaleAnimator().start();
    } else {
        getScaleAnimator().reverse();
    }
    return super.onDoubleTap(e);
}

我们运行看下效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,我们实现了哪里点击,哪里放大的效果;

初始状态双指放大


这是因为我们在执行 scale 方法的时候,没有进行是否是放大状态的判断以及改变;

@Override
public boolean onScale(ScaleGestureDetector detector) {
    if ((currentScale > smallScale && !isEnLarge) || (currentScale == smallScale && isEnLarge)) {
        isEnLarge = !isEnLarge;
    }
    // 缩放因子
    currentScale = initScale * detector.getScaleFactor();
    // 执行重新绘制
    invalidate();
    return false;
}

我们运行看下效果:

019c78a3-f58a-41f2-bf9b-7582eb66fccd.gif

好了,到这里 PhotoView 的能力基本上就全都实现了,在双指缩放的时候,大家可以自行加一个缩放动画的过度,让它看起来不那么生硬;

简历润色

熟练使用Gesture手势探测器实现复杂图片的处理;

下一章预告

我用自定义 View 画了一条鱼;

欢迎三连

来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~

从萤石云到GB28181:海康4G摄像机接入第三方平台的曲折之路 本文深入探讨了海康4G摄像机接入第三方平台时遇到的协议冲突问题,特别是GB28181与萤石云服务的互斥性。通过技术分析揭示了4G模块的定向流量限制和协议栈资源竞争等核心问题,并提供了有线接入测试和萤石云API二次开发等解决方案。最后总结了设备选型的关键参数和协议兼容性检查清单,为安防监控系统集成提供实用指导。 阅读详情

相关推荐

数字IC后端时钟树综合实战:OCC电路优化与时钟树平衡策略

本文深入探讨了数字IC后端设计中时钟树综合的关键挑战,重点分析了OCC电路时钟树优化的必要性。通过实战案例,详细阐述了如何通过手动干预、时钟分组与分段长树等策略,避免工具自动平衡导致的时序违例和资源浪费,从而有效实现时钟树平衡并提升设计质量。

weixin_29306261的博客 325

Android---PhotoView

自定义 PhotoView 继承(extends)自 View。并在最中间显示后面操作的图片。绘制图片可以重写 onDraw()方法,并在里面通过Canvas.drawBitmap()来要绘制图片。drawBitmap()的四个参数:bitmap: 要在 Canvas 中绘制的位图letf: 正在绘制的位图左侧的位置top: 正在绘制的位图顶部的位置paint: 画笔其中 (left, top) 是要绘制图片的起始坐标。要将图片绘制在中间,我们就需要计算 left/top 的位置。

qq_44950283的博客 2711

如何使用Ray和PyTorch训练模型

用户编写训练函数 → 定义单机训练逻辑。通过扩缩容配置 → 指定需要多少资源(Worker 数量、CPU/GPU)。训练器整合训练函数和配置 → 自动将训练函数分发到多个 Worker 上并行执行。Worker 运行训练函数 → 在分布式环境中完成模型训练。通过这种模式,Ray Train 实现了对分布式训练的抽象,用户只需关注单机训练逻辑,无需手动管理分布式通信和资源分配。

m0_62464865的博客 1922

Android开源控件:PhotoView的单击事件

PhotoView :PhotoView aims to help produce an easily usable implementation of a zooming Android ImageView.扩展自Android ImageView,支持通过单点/多点触摸来进行图片缩放的智能控件。 支持单点/多点触摸;缩放图片; 平滑滚动; 在滑动父控件下能够运行良好;(例如:ViewPage...

许进进的博客 6441

交通小白首投TRB就中Oral?我的完整时间线与避坑指南(附Editorial Manager实操截图)

本文详细解析了交通领域新手如何高效完成TRB国际会议投稿的全流程,从选题策略到投稿系统实操,再到评审过程解析和常见误区避坑指南。特别针对初次投稿者提供了实用建议,包括时间规划、选题考量、Editorial Manager系统操作技巧,以及如何应对评审意见。文章还分享了从录用到会议参与的关键步骤,帮助研究者提升投稿成功率。

weixin_27303545的博客 386

手把手教你安装 Vivado2022.2(附安装包)

Vivado 2022.2版本与之前的版本相比,具有以下几个显著的优势:电源设计管理器(PDM):Vivado 2022.2引入了全新的电源设计管理器(PDM),这是一个下一代功耗评估平台,为Versal® 和 Kria™ SOM提供了准确一致的功耗估计能力。PDM是Versal产品家族的首选功耗评估工具,为Versal ACAP硬IP块提供增强向导,简化从XPE向PDM的迁移,帮助用户获得准确的功耗评估。

dpwkj的博客 2万+

PhotoView设置点击事件

photoView内部已封装好点击事件,直接调用即可: photoView.setOnPhotoTapListener(new PhotoViewAttacher.OnPhotoTapListener() { @Override public void onPhotoTap(View view, float x, float y) {

wuliangAndroid 1756

SpringBoot + MyBatis-Plus 高效实现数据变更记录:从注解到通用审计方案(附源码)

*** 数据变更审计插件* 拦截增删改操作,自动记录变更日志*/@Component@Resource/*** 拦截SQL执行*/@Override/*** 数据变更审计插件* 拦截增删改操作,自动记录变更日志/*** 拦截SQL执行。

2601_94871597的博客 557

photoView的缩放和拖拽

package com.example.dragandscalephotodemo; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.MotionEvent; import uk.co.senab.photoview.PhotoView;

BOKE1的博客 1153

Qt应用软件【协议篇】自定义协议处理串口半包贴包

Qt串口数据,半包,贴包,丢包数据处理

小鱼酱的博客 1210

AutoSAR MemMap模块详解及使用注意事项

在本文中,我们将深入探讨MemMap模块的实例解析,并介绍在使用该模块时需要注意的一些事项。在上述代码中,我们使用了MEMMAP_START_SEC_VAR_32BIT和MEMMAP_START_SEC_CODE来定义MemMap区域的起始位置,使用MEMMAP_STOP_SEC_VAR_32BIT和MEMMAP_STOP_SEC_CODE来定义MemMap区域的结束位置。1.合理划分MemMap区域:根据实际需求,合理划分MemMap区域,将不同类型的变量放置在不同的区域中,以便于管理和访问。

weixin_50547796的博客 2530

理解PhotoView的核心,以双击事件为例

核心对于一个ImageView或者说View来说,view的大小是不变的,那图片如何实现大小的变化呢?答案就是Matrix。 Matrix的定义:字面意思是矩阵,在Android中表示使用矩阵的方法来对图片进行变换。可以略微想象一下,图片有很多个像素点,每个像素点都有它的值,将图片看作一个矩阵,那么对图片进行旋转,缩放实际上就是对矩阵进行变换。Android对这个变换过程封装进了Matrix类。

yi_shun的专栏 1983

原理和代码解读:用Diffusion Models高清图像修复(Image Inpainting)

在最近的Diffusion Models应用中,Image Inpainting+ControlNet/Lora等工作在模特衣服生成、家居场景重建等场景中得到了广泛的应用。这篇博客主要介绍Diffusion Models如何在修复高清图像的。

沉迷单车的追风少年 2496

2024届应届毕业生Android秋招总结,已成功入职网易云音乐

给大家送上我成功跳槽复习中所整理的资料,由于文章篇幅有限,所以只是把题目列出来了网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化学习资料的朋友,可以戳这里获取一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!我该怎么实现)eventbus的内部实现 一直问到不会~~~

FZBH_Q的博客 475

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

安卓面试题

接上一篇 往下写 http://blog.youkuaiyun.com/u011733020/article/details/45998861  ,   非常感谢在上一篇中给我指出问题的兄弟们。 面试公司:五道口  某公司 面试时间:5月27日 11:00 AM. 面试过程:                 感觉公司还是挺有活力的的,进去填了申请表,人事

山鹰的专栏 723

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

面试题kkk

这篇文章摘要主要涵盖了Android开发中的核心知识点和技术难点,包括数据结构、Java与Kotlin语言特性、多线程与并发、内存管理、安卓组件与UI、IPC通信、数据库、多媒体处理、设计模式、性能优化、跨端开发等多个方面。文章详细解析了常见数据结构的使用与实现、JVM机制、内存泄漏分析与解决方案、Activity生命周期、事件分发机制、Handler原理、音视频处理、设计模式应用等核心内容,并提供了实际项目中的技术卡点与解决方案。此外,还涉及了性能优化策略、跨端开发差异、技术方案设计思路以及Git常用命令

mix39的博客 929

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

腾讯客户端开发面试复盘:从基础到架构的全面考察与应对策略

在软件开发领域,扎实的计算机基础是构建高质量应用的基石。理解数据结构与算法、操作系统原理、网络协议等核心概念,是解决复杂工程问题的前提。这些基础原理在客户端开发中体现为对性能、内存和用户体验的极致追求,其技术价值在于能够设计出高效、稳定的应用程序。尤其在移动端场景下,面对多线程管理、网络优化、渲染性能等挑战,深入理解底层机制显得尤为重要。本文通过一次典型的腾讯客户端开发面试复盘,结合**项目深挖**和**系统设计**等高频考察点,详细拆解了从计算机基础到移动端特有问题(如Handler机制、图片缓存)的面试

weixin_34059951的博客 396

北京Android各招聘公司面试实战总结2

面试公司:五道口  某公司 面试时间:5月27日 11:00 AM. 面试过程:                 感觉公司还是挺有活力的的,进去填了申请表,人事就跟我聊了下 从上家离职原因(找工作 要准备怎么回答这个问题,最好能找一些客观因素,不要说 感觉以前公司不好。。。)   随后 就是 android 技术 跟我面试。大概面试了 四十分钟吧。惯例 总结了一下

ArtLife 1150

PhotoView的点击事件遇到的问题

问题描述 功能实现 总结问题描述项目中有个查看图片的功能,之前的同事用的PhotoView + ViewPager 实现的,现在我要补充的一个功能就是点击其中一张图片然后查看原图(带转场动画),再点击一次就回到原来的页面。功能实现点击跳转大图,包括转场动画和简单实现,我们再次就不赘述了,如果遇到问题了,可以看看我这篇文章 关于activity转场动画makeSceneTransitionAnima

批判是一个巴掌的专栏 7775

PhotoView单击退出 点击事件无效解决方法

photoView是个很好的工具,相信很多人都用过,现在项目中需要单击图片退出,于是就果断给图片加了个点击事件: imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { fini

lvkaixuan的博客 3208

Android 14+ 模拟器抓包实录:把 Burp CA 装成系统 CA,让 targetSdk≥34 的 App 乖乖解密

本文记录了在 Android 14+ 环境下,通过将 Burp CA 安装为系统信任锚,成功抓取 targetSdk=35 App 的 HTTPS 明文流量的完整实操过程。针对“浏览器可用、App 不可用”的典型现象,精准识别出根本原因是系统级证书信任策略变更(用户安装的 CA 被屏蔽),而非 SSL Pinning。仅通过 adb 命令将 CA 推送至系统证书存储并重启生效,无需 Frida、Objection 或修改 APK,实现零侵入解密。全文命令均基于 Windows + MuMu 模拟器环境实测通

JIANqiao19931029的博客 341

具身智能 2026:启元人形机器人开发者生态的“安卓时刻”来了吗

一个贯穿全文的问题:人形机器人开发者生态,是否正从「封闭垂直」走向「开源平台化」?本文以开源底座的公开数据为标尺,盘点 SDK、URDF、仿真与技能分发的开放程度,并落回启元机器人的开源路径。所有关键数据均标注来源,文末附社区与学堂链接。

在北风吹起的时候加入我们的队伍 在秋雨到来的时候成为彼此的温暖 743

android-ad-cleaner:android免 Root 批量关闭系统开屏与营销推送

橘子,计算机视觉方向工程师,深耕 AIGC 与多媒体处理领域,专注技术实用化落地。本文为原创内容,转载请注明来源与作者。优快云 首发 | 版权所有 © 2026。

下载资源或查看教程前,请务必阅读置顶的《免责声明》 498

Android 14 SELinux策略深度解析

init 的全局公开约束(如 neverallow init execute_no_trans,放 public 才能约束所有分区的规则)这就是"哪些可见"的强制机制。alps_s0(MTK 工程)的 device/mediatek/sepolicy/basic/ 提供。所有分区(system/system_ext/product/vendor)都可引用。init的公开约束、跨分区共用的 type/attribute/neverallow。——vendor 的 .cil 只能引用 plat 的。

新时代农民工 102

Android 开发入门实践(二):Kotlin 空安全,从可空类型到安全访问

延续 NewsApp 项目中的新闻标题练习,记录 Kotlin 安全调用。

Easyhood的博客 276

Android 模块化收口:能力组合框架、手写 LifecycleOwner 与热修复共存

二十多个模块业务高度相似却各有差异,核心矛盾在于“统一与灵活”的平衡。通过角色接口组合(如IProjectAware、IVoiceAware)实现能力按需注入,新增功能零侵入;用模板方法锁定生命周期流程,确保顺序一致;结合@CallSuper与工厂方法,既约束关键逻辑,又保留子类自由。最终实现:骨架统一、填空自由,管得住业务,又不侵入业务。

mmsx990的博客 395
上一篇: 大龄程序员的2024年1月总结:转岗、热榜、分享、健康、技术文章
下一篇: 如何应对Android面试官->实战高级UI,用自定义View画一条锦鲤(上)
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值