仿QQ未读消息拖拽删除粘性效果

UI--学习模仿QQ未读提醒拖拽删除 作为一款优秀的社交聊天软件,QQ始终保持着优秀的交互与设计,同时引领不少新时尚与标准规范,特别是它也有一些人性化的设计颇值得为人称道。今天要提到的是 QQ消息未读拖拽清除(一键退朝”,“一键清除未读”,“一键下班”)的功能。具体细节参考 知乎:一键消除红点功能是怎么想出来的?当然,得益于诸位大大的各种尝试,小弟也稍加模仿修改了一个类似的Demo.这里展示下我们最后实现的各种样式及效果图 阅读详情

1. activity_main中的代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/rl"
        android:layout_width="match_parent"
        android:layout_height="60dp">

        <com.wj.sticky.StickyFlagView
            android:id="@+id/sticky_view"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_centerVertical="true"
            app:flagRadius="10dp"
            app:flagTextSize="15sp" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="This is a RelativeLayout"
            android:textSize="20sp" />
    </RelativeLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@android:color/darker_gray"/>

    <FrameLayout
        android:id="@+id/fl"
        android:layout_width="match_parent"
        android:layout_height="60dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="This is a FrameLayout"
            android:textSize="20sp" />

        <com.wj.sticky.StickyFlagView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_gravity="center_vertical"
            app:flagColor="#f74c31"
            app:flagDrawable="@drawable/bubble" />
    </FrameLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@android:color/darker_gray"/>
</LinearLayout></span>

局中的StickyFlagView是一个封装好的未读消息拖拽控件,在这里我们先来了解一下它的xml属性。

(1)flagColor :标记的颜色(包括黏着线和黏着点)
(2)flagTextColor:标记的文本颜色
(3)flagTextSize:标记的文本大小
(4)flagRadius:标记的半径,设置此属性后,将按照给定的半径绘制一个标记圆点。如果同时设置了flagDrawable属性,则此属性失效。
(5)flagDrawable:为标记指定图片,设置此属性后,将绘制一个图片标记。
(6)maxStickRadius:黏着点的最大黏着半径
(7)minStickRadius:黏着点的最小黏着半径
(8)maxDistance:标记最大的拖拽距离

2. MainActivity.Java文件

public class MainActivity extends AppCompatActivity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        final StickyFlagView sfv = (StickyFlagView) findViewById(R.id.sticky_view);  
        sfv.setOnFlagDisappearListener(new StickyFlagView.OnFlagDisappearListener() {  
            @Override  
            public void onFlagDisappear(StickyFlagView view) {  
                Toast.makeText(MainActivity.this, "Flag have disappeared.", Toast.LENGTH_SHORT).show();  
            }  
        });  
        sfv.setFlagText("6");  
    }  

}

设置了未读消息标记的文本和删除后的回调监听。

3. StickyFlagView的具体实现

private Context context;  

private ViewGroup parent;  
private ViewGroup.LayoutParams originalLp; // view原始layout  
private int[] originalLocation; // view原始的location  
private int originalWidth; // view原始的宽度  
private int originalHeight; // view原始的高度  

private float stickRadius; // 黏贴半径  

private int flagColor; // 标记颜色  
private int flagTextColor; // 标记文本颜色  
private float maxDragDistance; // 最大拖拽距离  
private String flagText; // 标记文本  
private float flagTextSize; // 标记文本大小  
private float flagRadius; // 标记半径  
private Bitmap flagBitmap; // 标记图片  
private float maxStickRadius; // 最大黏贴半径  
private float minStickRadius; // 最小黏贴半径  
private float rate = 0.8f;  

private boolean isFirstSizeChange = true;  
private boolean isTouched;  
private boolean isReachLimit; // 是否达到最大拖拽距离  
private boolean isRollBackAnimating; // 回滚动画是否在执行  
private boolean isDisappearAnimating; // 消失动画是否在执行  
private boolean isFlagDisappear; // 标记是否消失  
private boolean isViewLoadFinish; // view是否加载完毕  
private boolean isViewInWindow; // view是否在window中  

private int which;  
private List<Integer> disappearRes;  

private PointF stickPoint; // 黏贴点  
private PointF dragFlagPoint; // 拖拽标记点  
private Paint flagPaint; // 标记画笔  
private Paint flagTextPaint; // 标记文本画笔  
private Path flagPath;  

private OnFlagDisappearListener listener;  
private WindowManager windowManager;  

public StickyFlagView(Context context) {  
    super(context);  
    this.context = context;  
    init();  
}  

public StickyFlagView(Context context, AttributeSet attrs) {  
    super(context, attrs);  
    this.context = context;  

    initViewProperty(context, attrs);  
    init();  
}  

public StickyFlagView(Context context, AttributeSet attrs, int defStyleAttr) {  
    super(context, attrs, defStyleAttr);  
    this.context = context;  

    initViewProperty(context, attrs);  
    init();  
}  

/** 
 * 初始化view属性 
 */  
private void initViewProperty(Context context, AttributeSet attrs) {  
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StickyFlagView);  

    flagColor = typedArray.getColor(R.styleable.StickyFlagView_flagColor, Color.RED);  
    flagTextColor = typedArray.getColor(R.styleable.StickyFlagView_flagTextColor, Color.WHITE);  
    flagTextSize = typedArray.getDimension(R.styleable.StickyFlagView_flagTextSize, ScreenUtils.spTopx(context, 12));  
    maxDragDistance = typedArray.getDimension(R.styleable.StickyFlagView_maxDistance, ScreenUtils.getScreenHeight(context) / 6);  
    minStickRadius = typedArray.getDimension(R.styleable.StickyFlagView_minStickRadius, ScreenUtils.dpToPx(context, 2));  
    flagRadius = typedArray.getDimension(R.styleable.StickyFlagView_flagRadius, ScreenUtils.dpToPx(context, 10));  
    maxStickRadius = typedArray.getDimension(R.styleable.StickyFlagView_maxStickRadius, flagRadius * rate);  

    Drawable flagDrawable = typedArray.getDrawable(R.styleable.StickyFlagView_flagDrawable);  
    if (flagDrawable != null) {  
        flagBitmap = ((BitmapDrawable) flagDrawable).getBitmap();  
    }  

     typedArray.recycle();  
}  

private void init() {  
    // 处理onDraw方法不执行的问题  
    setWillNotDraw(false);  

    // 这些默认值是为第一个构造函数准备的  
    if (flagColor == 0) {  
        flagColor = Color.RED;  
    }  
    if (flagTextColor == 0) {  
        flagTextColor = Color.WHITE;  
    }  
    if (flagTextSize == 0) {  
        flagTextSize = ScreenUtils.spTopx(context, 12);  
    }  
    if (flagRadius == 0) {  
        flagRadius = ScreenUtils.dpToPx(context, 10);  
    }  
    if (maxDragDistance == 0) {  
        maxDragDistance = ScreenUtils.getScreenHeight(context) / 6;  
    }  
    if (minStickRadius == 0) {  
        minStickRadius = ScreenUtils.dpToPx(context, 2);  
    }  
    if (maxStickRadius == 0) {  
        maxStickRadius = flagRadius * rate;  
    }  

    originalLocation = new int[2];  
    stickPoint = new PointF();  
    dragFlagPoint = new PointF();  
    flagPath = new Path();  

    flagPaint = new Paint();  
    flagPaint.setAntiAlias(true);  
    flagPaint.setColor(flagColor);  

    flagTextPaint = new Paint();  
    flagTextPaint.setAntiAlias(true);  
    flagTextPaint.setColor(flagTextColor);  
    flagTextPaint.setTextSize(flagTextSize);  

    windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
}  

上面的代码主要是初始化一些成员变量,拿到自定属性值,设置一些默认值。需要注意的是init()方法中setWillNotDraw(false),这段代码不能缺少,在高版本SDK中,invalid方法可能不能促使view的onDraw方法执行。大家可能注意到成员变量中有这么两个属性:parent和originalLp,这两个属性的作用是什么呢?大家可以想象一下QQ的未读消息拖拽效果,可以从自己所在的item中拖出来,并不受父控件大小的束缚。这种效果要借助WindowManager把StickyFlagView加入到window中,并把StickyFlagView的宽高设置为match。但是一个孩子不能有两个父亲,所以我们要先把view从父控件中移除,再把view添加到window中,等到拖拽效果完成之后,再把view从window中移除,然后还给原来的父亲。parent属性就用来保存view本来的父控件,originalLp用来保存view的布局参数。那么在什么时机把view加入window,又在什么时机把view加入原来的父控件呢,让我们接着看代码。

@Override  
public boolean onTouchEvent(MotionEvent event) {  
    if (!isViewLoadFinish || isRollBackAnimating || isDisappearAnimating || isFlagDisappear) {  
        return true;  
    }  
    switch (event.getAction()) {  
        case MotionEvent.ACTION_DOWN:  
            isTouched = true;  
            addViewInWindow();  
            break;  
        case MotionEvent.ACTION_MOVE:  
            dragFlagPoint.x = event.getRawX();  
            dragFlagPoint.y = event.getRawY() - ScreenUtils.getStatusHeight(context);  

            double distance = Math.sqrt(Math.pow(dragFlagPoint.y - stickPoint.y, 2) + Math.pow(dragFlagPoint.x - stickPoint.x, 2));  
            if (distance > maxDragDistance) {  
                isReachLimit = true;  
            } else {  
                isReachLimit = false;  
                stickRadius = (float) (maxStickRadius * (1 - distance / maxDragDistance));  
                stickRadius = stickRadius < minStickRadius ? minStickRadius : stickRadius;  
            }  

            postInvalidate();  
            break;  
        case MotionEvent.ACTION_UP:  
            isTouched = false;  
            if (isReachLimit) {  
                launchDisappearAnimation(1000);  
                if (listener != null) {  
                    listener.onFlagDisappear(this);  
                }  
            } else {  
                launchRollBackAnimation(300);  
            }  
            break;  
    }  
    return true;  
}

这里重写了view的onTouchEvent方法,在down事件触发时调用addViewInWindow方法,这个方法的作用就是把view从当前的父控件中移除,然后加入到window中,view的大小充满整个window,这样我们就拥有了一个面积足够大的悬浮画板,以便我们随心所欲的在上面进行绘制。
当move事件触发时,我们记录了拖拽点dragFlagPoint,这个坐标是用于不断重绘标记的位置。获得拖拽点坐标时,使用了event.getRawY(),然后减去状态栏的高度。一开始我使用的是event.getY(),不过拿到的坐标点出现了极大的误差,按理说view的大小是充满window的,使用getY和getRawY应该只是一个状态栏高度的差别。我也不知道这是为什么,有知道的朋友可以告诉我一下。好了,让我们继续,得到dragFlagPoint后,我们计算了它与黏着点(stickPoint)的距离distance,stickPoint的初始值是view还在原父控件中时的自身的中心点,当view加入到window时stickPoint的坐标要修正一下,不然view的大小发生了变化,再按原来的坐标绘制,黏着点肯定要跑偏了,这部分的逻辑我们稍后会讲。isReachLimit是个标识位,当distance大于最大拖拽距离时把它设为true,否则设为false,并根据distance改变黏着点半径的大小。
当up事件触发时,我们会判断是否达到了极限距离,如果达到了调用launchDisappearAnimation方法,这个方法是标记的一个删除时的动画,否则执行标记的回弹动画。两个动画执行完毕后都会做同一件事情,那就是调用restoreView方法,把view从window中移除,然后把view还给原来的父亲。
上面提到了addViewInWindow和restoreView方法,下面贴出它们的代码

private void addViewInWindow() {  
    if (isViewLoadFinish && !isViewInWindow) {  
        if (parent != null) {  
            // 将view从它的父控件中移除  
            parent.removeView(this);  
        }  

        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();  
        layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;  
        layoutParams.format = PixelFormat.TRANSPARENT;  
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;  
        layoutParams.gravity = Gravity.START | Gravity.TOP;  
        layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;  
        layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;  
        layoutParams.x = 0;  
        layoutParams.y = 0;  

        if (windowManager != null) {  
            // 将view加入window  
            windowManager.addView(this, layoutParams);  
            post(new Runnable() {  
                @Override  
                public void run() {  
                    isViewInWindow = true;  
                }  
            });  
        }  
    }  
}  


private void restoreView() {  
    if (isViewLoadFinish) {  
        // 还原黏贴半径  
        stickRadius = flagRadius > maxStickRadius ? maxStickRadius : flagRadius * rate;  
        isReachLimit = false;  

        if (windowManager != null && isViewInWindow) {  
            // 把view从window中移除  
            windowManager.removeView(this);  
            isViewInWindow = false;  

            if (parent != null) {  
                parent.addView(this, originalLp);  
                // 在高版本的SDK上,没有这段代码,view可能不会刷新  
                post(new Runnable() {  
                    @Override  
                    public void run() {  
                        parent.invalidate();  
                    }  
                });  
            }  
        }  
    } else {  
        post(new Runnable() {  
            @Override  
            public void run() {  
                restoreView();  
            }  
        });  
    }  
}

上面代码中的isInWindow是为了防止不断触发down事件,造成view重复加入window发生异常。isViewLoadFinish是判断view是否还在加载中,我们知道一次点击事件就是一个down和一个up,down的时候我们把view加入window,up时我们把view加入parent,view的加载需要时间,windowManager.addView()调用后,view开始加载,在加载过程中,restoreView方法调用,如果没有isViewLoadFinish这个判断,那么执行windowManner.removeView()就会出现问题。
parent.addView()和windowManager.addView()都会触发以下三个方法执行

@Override  
public void setLayoutParams(ViewGroup.LayoutParams params) {  
    super.setLayoutParams(params);  

    isViewLoadFinish = false;  
    this.post(new Runnable() {  
        @Override  
        public void run() {  
            isViewLoadFinish = true;  
        }  
    });  
}  

@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    int width = 0;  
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
    if (widthMode == MeasureSpec.UNSPECIFIED) {  
        if (flagBitmap == null) {  
            width = (int) ScreenUtils.dpToPx(context, 20);  
        } else {  
            width = flagBitmap.getWidth();  
        }  
    } else if (widthMode == MeasureSpec.EXACTLY){  
        width = MeasureSpec.getSize(widthMeasureSpec);  
    } else if (widthMode == MeasureSpec.AT_MOST) {  
        if (flagBitmap == null) {  
            width = (int) ScreenUtils.dpToPx(context, 20);  
        } else {  
            width = flagBitmap.getWidth();  
        }  
    }  

    int height = 0;  
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
    if (heightMode == MeasureSpec.UNSPECIFIED) {  
        if (flagBitmap == null) {  
            height = (int) ScreenUtils.dpToPx(context, 20);  
        } else {  
            height = flagBitmap.getHeight();  
        }  
    } else if (heightMode == MeasureSpec.EXACTLY){  
        height = MeasureSpec.getSize(heightMeasureSpec);  
    } else if (heightMode == MeasureSpec.AT_MOST) {  
        if (flagBitmap == null) {  
            height = (int) ScreenUtils.dpToPx(context, 20);  
        } else {  
            height = flagBitmap.getHeight();  
        }  
    }  

    setMeasuredDimension(width, height);  
}  

@Override  
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {  
    super.onSizeChanged(width, height, oldWidth, oldHeight);  
    if (isFirstSizeChange) {  
        parent = (ViewGroup) getParent();  
        // StickyFlagView的父控件只能是RelativeLayout或FrameLayout  
        if (!(parent instanceof RelativeLayout || parent instanceof FrameLayout)) {  
            throw new RuntimeException("StickyFlagView can only be placed on the RelativeLayout or FrameLayout.");  
        }  

        // 记录view原始layout参数  
        originalLp = getLayoutParams();  
        originalWidth = width;  
        originalHeight = height;  

        getLocationOnScreen(originalLocation);  
        originalLocation[1] = originalLocation[1] - ScreenUtils.getStatusHeight(context);  

        if (flagBitmap == null) {  
            float radius = Math.min(originalWidth, originalHeight) * 0.5f;  
            flagRadius = flagRadius > radius ? radius : flagRadius;  
            stickRadius = flagRadius > maxStickRadius ? maxStickRadius : flagRadius * rate;  
        } else {  
            // 黏贴半径不能超过图片宽和高的最小值的一半  
            flagRadius = Math.min(flagBitmap.getWidth(), flagBitmap.getHeight()) * 0.5f;  
            stickRadius = maxStickRadius > flagRadius ? flagRadius * rate : maxStickRadius;  
        }  

        // 黏贴点在原始view的中心点  
        stickPoint.set((float) (originalWidth * 0.5), (float) (originalHeight * 0.5));  
        isFirstSizeChange = false;  
    } else {  
        // view的size改变之后,修正黏贴点坐标  
        if (originalWidth == width && originalHeight == height) {  
            stickPoint.set((float) (originalWidth * 0.5), (float) (originalHeight * 0.5));  
        } else {  
            stickPoint.x += originalLocation[0];  
            stickPoint.y += originalLocation[1];  
        }  
    }  
    dragFlagPoint.x = stickPoint.x;  
    dragFlagPoint.y = stickPoint.y;  
}

上面三个方法的调用顺序是setLayoutParams,onMeasure,onSizeChanged 大家可以看到isViewLoadFinish的值是在setLayoutParams中设置的,post方法将一个Runnable加入队列,当view加载完毕后,队列中的runnable会依次执行。onMeasure方法里面的代码应该很好理解,当指定了StickyFlagView的宽高后,我们就用指定的宽高,如果没有我们就给一个默认的。在onSizeChanged方法中,大家可以看到parent和originalLp是在size第一次被改变是初始化的,与此同时还记录了view在屏幕中的位置,记录这个位置是为了在view加入window后,修正黏着点的位置,黏着点的初始位置是view的中心点,下面上一张图解释一下

参考; http://www.bubuko.com/infodetail-1092644.html

uniapp实现列表拖拽排序+滑动删除功能 此篇代码在原插件基础进行了bug修改与滑动功能的新增 原插件地址 HM-dragSorts.vue组件使用 HM-dragSorts.vue <template> <view class=""> <view class="HM-drag-sort" :style="{'height': ListHeight+'rpx','background-color': listBackgroundColor}"> <!-- 拖拽中显示的行 --> 阅读详情

相关推荐

仿QQ未读消息桌面图标显示

仿QQ桌面图标显示未读消息,可以根据未读消息来修改桌面应用图标

Android仿QQ未读消息拖拽删除粘性效果

这种效果已经有很多人实现了,网上相关的博文也不少。今天,我就站在巨人的肩膀上再稍微做一些优化和扩展。有写的不对的地方还望大家指正。 先来看一下效果图:                                             下面让我们来看一下代码: 先看一下布局文件activity_main: <LinearLayout xmlns:android=

xiaowantong1991的博客 6089

Android仿QQ消息列表ListView滑动删除效果

Android仿QQ消息列表ListView滑动删除效果,具体详见:http://blog.youkuaiyun.com/top_code/article/details/19084879

uniapp移动端实现商品拖拽集合,一行多个商品左滑删除功能!

的拖拽集合,以及商品的左滑删除。

徐飞的博客 751

jQueryQQdragjQuery实现的课拖拽层效果(仿QQ空间模块拖动)

jQueryQQdragjQuery 实现的课拖拽层效果(模仿QQ空间的模块拖动功能)

仿QQ微信手机端触屏滑动效果

模仿QQ微信手机端触屏滑动效果(已读,删除,取消关注) 模仿QQ微信手机端触屏滑动效果(已读,删除,取消关注)

微信小程序、uniapp使用touchstart和touchmove左右滑动删除。以及解决上下抖动问题。

微信小程序、uniapp使用touchstart和touchmove左右滑动删除。以及解决上下抖动问题。

早晚的博客 5839

仿QQ未读消息拖动

仿QQ未读消息拖动,可以反弹。只有一个代码类,自定义的View,大家可以新建工程试试。

uniapp实现滑动删除

uniapp如何实现滑动删除效果

toutudayuan的博客 1231

支付宝首页(仿)添加、删除、拖拽等功能

仿支付宝首页里的更多功能,共享出来帮助你我他 仿支付宝首页里的更多功能,共享出来帮助你我他

Android 仿QQ首页的消息和电话的切换,首页的头部(完全用布局控制)

Android 仿QQ首页的消息和电话的切换,首页的头部(完全用布局控制)

仿QQ拖动删除未读消息个数气泡之二

在仿QQ拖动删除未读消息个数气泡这篇文章中,模仿了QQ的删除未读消息气泡,不过也遗留了一个问题,当时为了让气泡能够在全屏范围内拖动,不能将其放在布局文件xml中,而是采用了在主布局加载完成后用addView方法动态加载气泡,这种方式不太好,因为它需要自己计算在全屏范围中,气泡放在什么地方,这需要参造物,而且如果把气泡放在listview中的话,也不可能给每个Item都去动态计算增加气泡,所以这种方

gesanri的专栏 4916

仿QQ消息未读拖拽清除,“一键退朝”,“一键下班”

blog原文地址 :http://blog.youkuaiyun.com/qiaoidea/article/details/46608385 (效果图: https://raw.githubusercontent.com/Qiaoidea/QQTipsView/master/QQTips_demo.gif) 仿QQ消息未读拖拽清除,“一键退朝”,“一键下班”

Android自定义View仿QQ消息拖拽气泡实现

Android自定义View仿QQ消息拖拽气泡实现 很多小的效果看上去很酷炫,其实操作起来很简单,就是要注意细节以及分析状态,然后去绘制,这里会实现qq消息拖拽气泡的实现。画图分析 源代码 效果展示 画图分析 在画图之前,看一下原效果图,分析实现步骤 由图可知,它这里拖拽画的是图片,为了更接近原理以及自定义可扩展,我们将拖拽的99+也当成一个圆。如下图:小圆是在原始位置,大圆是在当前手指的位

3752

仿QQ消息提醒小红点拖拽粘连效果

仿QQ消息提醒小红点拖拽粘连效果 [注意:本资源来自网络,如有侵权,请联系我删除,谢谢。]

Android仿QQ未读消息–红点拖拽删除【源代码】

本Demo是一款仿qq未读消息拖拽删除的例子,继承RelativeLayout的WaterDrop实现了圆形图标功能、继承ImageView的CircleImageView圆形图片功能、效果非常不错、很适合有圆形设计的哥们、效果图片如下 CircleImageView核心代码 private void updateShaderMatrix() { float scale; float dx = 0; float dy = 0; mShaderMatrix.set(null); if (mBitmapWidth * mDrawableRect.height() > mDrawabl

Android 仿QQ未读消息拖拽删除粘性控件效果

效果图: 分析  一 : 1、应用的地方:如未读数据的清除等 2、这个控件要实现哪些功能呢? 1)拖拽超出范围时,断开了,此时我们松手,图标消失 2)拖拽超出范围时,断开了,此时我们把图标移动回去,图标恢复原样 3)拖拽没有超出范围时,此时我们松手,图标弹回去 3、如何实现: 1)我们先画个两个静态的圆圈,一个大的,一个小的 2)绘制中间连接的部分: 3)

豆子的博客 1438

瑞芯微RK3568与RK3566芯片选型指南:从接口差异到应用场景深度解析

在嵌入式系统与边缘计算领域,芯片选型是决定产品成败的关键一步。其核心在于理解芯片的架构、接口扩展能力与系统可靠性之间的平衡。以瑞芯微(Rockchip)的RK3568和RK3566为例,这两款同为四核A55架构的芯片,在基础算力上相近,但其面向的应用赛道却因接口配置而截然不同。RK3568凭借其双千兆网口、PCIe 3.0高速接口以及全链路ECC内存支持,在技术价值上更侧重于构建高可靠、高扩展性的系统,如边缘计算网关、工业控制器和网络视频录像机(NVR)。相比之下,RK3566则以其精简的接口配置,精准服务

weixin_33314040的博客 480

仿QQ未读消息拖拽效果

https://blog.youkuaiyun.com/wangwo1991/article/details/79438087

Mr_ziheng的博客 411

ScanNet数据集下载全攻略:从申请到解析的保姆级教程(含常见问题解决)

本文提供了ScanNet数据集从申请授权到下载、解析的完整实战指南。针对这个大规模室内RGB-D数据集,详细介绍了官方申请流程、优化下载策略、数据验证方法,并重点解决了深度图解析等常见问题,帮助三维视觉研究者高效获取和使用这一宝贵资源。

5f4d3s2a1q的博客 620

Mem0 vs LangMem vs Letta:三大AI记忆框架实战对比(附代码示例)

本文深入对比了Mem0、LangMem和Letta三大AI记忆框架的核心特性与适用场景。Mem0提供开箱即用的记忆管理服务,适合快速集成;LangMem深度融入LangGraph生态,适合复杂工作流;Letta则采用虚拟内存系统,旨在突破上下文窗口限制。文章通过实战代码示例,为开发者选择适合其业务需求的AI记忆解决方案提供了清晰指南。

g9h0i1的博客 680
上一篇: google官方的下拉刷新+自定义上拉加载更多
下一篇: 阿乐卫士项目
CoderLiLe
博客等级 码龄11年 2粉丝 26原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值