Android 4.0 Launcher2源码分析——桌面快捷图标的拖拽

本文来自http://blog.csdn.net/chenshaoyang0011 转载请申明文章出处!

通过上一篇文章Android4.0Launcher2源码分析(五)——Workspace的滑动中,已经了解了LauncherViewTree中各层所负责的工作,在DragLayer中就负责对快捷图标和AppWidget等组件的拖拽工作。桌面的滑动和图标的拖拽是两项独立的工作,正常情况下我们用手指滑动桌面会触发滑动操作,而当长按一个图标时,则会触发图标的拖拽操作,此时再滑动则会拖拽图标移动而桌面不会滑动。那么这里就分两大部分来探讨:1、拖拽操作的启动。2、拖拽。


一、拖拽操作的启动

那么首先进入Launcher.onCreate()中来探究下如何激活拖拽的状态。

protected void onCreate(Bundle savedInstanceState) {......setupViews();......}
接着进入setupViews();
private void setupViews() {......mWorkspace.setOnLongClickListener(this);......}
从这里我们可以看到对Workspace设置了OnLongClickListener,而Launcher又实现了这个接口。接着进入Launcher.onLongClick()
public boolean onLongClick(View v) {......if (!(v instanceof CellLayout)) {v = (View) v.getParent().getParent();}resetAddInfo();CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();......// The hotseat touch handling does not go through Workspace, and we always allow long press// on hotseat items.final View itemUnderLongClick = longClickCellInfo.cell;boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();if (allowLongPress && !mDragController.isDragging()) {if (itemUnderLongClick == null) {......} else {if (!(itemUnderLongClick instanceof Folder)) {// User long pressed on an itemmWorkspace.startDrag(longClickCellInfo);}}}return true;}
当用户在一个item上长按时,则itemUnderLongClick != null,再通过调用Workspace.startDrag()来激活item的拖拽。下面先通过时序图来看下拖拽状态激活所经历的过程:


图标拖拽功能的激活大概可以分为六步,下面就一步一步的探究下其中的实现:

Step1:Workspace.startDrag(CellLayout.CellInfo cellInfo)

void startDrag(CellLayout.CellInfo cellInfo) {View child = cellInfo.cell;......mDragInfo = cellInfo;//使图标从桌面上消失,给人一种被“拖到空中”的感觉child.setVisibility(GONE);......final Canvas canvas = new Canvas();// We need to add extra padding to the bitmap to make room for the glow effectfinal int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;// The outline is used to visualize where the item will land if dropped//图标的轮廓,在桌面上的对应的位置绘制图标的轮廓,显示当手松开图标时它在桌面上的落点mDragOutline = createDragOutline(child, canvas, bitmapPadding);beginDragShared(child, this);}
在这个方法中,主要的工作就是让图标从桌面上消失,并且显示一个图标的外部轮廓,以表明它将要放置的位置,其显示的效果如下:


显示图标的轮廓可以从视觉上给用户更加好的体验。接着,进入beginDragShared()

Step2:Workspace.beginDragShared(View child,DragSource source)

public void beginDragShared(View child, DragSource source) {......// The drag bitmap follows the touch point around on the screenfinal Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);final int bmpWidth = b.getWidth();//我们将在DragLayer中绘制“拖拽后”的图标,通过DragLayer.getLoactionInDragLayer()//获取在DragLayer中的坐标,并存放在mTempXY中。mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;int dragLayerY = mTempXY[1] - bitmapPadding / 2;Point dragVisualizeOffset = null;Rect dragRect = null;//无论child是BubbleTextView或者PagedViewIncon或者FolderIcon的实例//定位图标的位置与大小if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);int top = child.getPaddingTop();int left = (bmpWidth - iconSize) / 2;int right = left + iconSize;int bottom = top + iconSize;dragLayerY += top;// Note: The drag region is used to calculate drag layer offsets, but the// dragVisualizeOffset in addition to the dragRect (the size) to position the outline.dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);dragRect = new Rect(left, top, right, bottom);} else if (child instanceof FolderIcon) {int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);dragRect = new Rect(0, 0, child.getWidth(), previewSize);}......mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);b.recycle();}
Workspace.beginSharedDrag()中主要所做的工作就是计算拖拽目标位于DragLayer中的坐标和尺寸大小,接着又调用DragController.startDrag()

Step3:DragController.startDrag(Bitmap b ,int dragLayerX, int dragLayerY,DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion)

    /*** Starts a drag.** @param b The bitmap to display as the drag image.  It will be re-scaled to the*          enlarged size.* @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.* @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.* @param source An object representing where the drag originated* @param dragInfo The data associated with the object that is being dragged* @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or*        {@link #DRAG_ACTION_COPY}* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.*          Makes dragging feel more precise, e.g. you can clip out a transparent border*/public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {......for (DragListener listener : mListeners) {listener.onDragStart(source, dragInfo, dragAction);}final int registrationX = mMotionDownX - dragLayerX;final int registrationY = mMotionDownY - dragLayerY;final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;//设置mDragging=true,表示拖拽已经开始//在DragLayer的onInterceptTouchEvent()中根据这个值判断是否拦截MotionEventmDragging = true;//实例化DragObject,表示拖拽的对象//封装了拖拽对象的信息mDragObject = new DropTarget.DragObject();mDragObject.dragComplete = false;mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);mDragObject.dragSource = source;mDragObject.dragInfo = dragInfo;......final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,registrationY, 0, 0, b.getWidth(), b.getHeight());......//将拖拽的图标显示在DragLayer中dragView.show(mMotionDownX, mMotionDownY);handleMoveEvent(mMotionDownX, mMotionDownY);}

代码中显示通过一个for语句调用了DragListener.onDragStart()方法,通知它们已经开始拖拽了,其中由于Workspace实现了DragListener并且添加到了mListeners中。所以Workspace.onDragStart()被调用。然后又封装了一个DragObject对象,封装DragSourceDragInfoDragView等信息。接着,将调用DragView.show()DragView显示在DragLayer中。


Step4:DragView.show(int touchX,int touchY)

/*** Create a window containing this view and show it.** @param touchX the x coordinate the user touched in DragLayer coordinates* @param touchY the y coordinate the user touched in DragLayer coordinates*/public void show(int touchX, int touchY) {//将DragView添加到DragLayer中mDragLayer.addView(this);//设置位置、尺寸等信息DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);lp.width = mBitmap.getWidth();lp.height = mBitmap.getHeight();lp.x = touchX - mRegistrationX;lp.y = touchY - mRegistrationY;lp.customPosition = true;setLayoutParams(lp);mLayoutParams = lp;mAnim.start();}

其中的内容很简单易懂,就是在将DragView添加到了DragLayer中,并且在合适的位置显示了出来。接着应该调用在DragController.startDrag()中调用handleMoveEvent(),这个将在后文将拖拽过程分析时在看。到这一步,拖拽操作的启动过程就完成了。接着就可以拖拽图标了。


二、拖拽

通过了前面文章的分析,已经知道了拖拽过程的实现在DragLayer中,当进行图标的拖拽时,DragLayer.onInterceptTouchEvent()就会对MotionEvent进行拦截。并且

在自身的onTouchEvent()方法中进行操作,从而实现图标的移动。由于onInterceptTouchEvent()拦截了MotionEvent,因此WorkspaceUI控件不会接收到事件,从而不会产生

干扰。那么首先进入DragLayer.onInterceptTouchEvent()


public boolean onInterceptTouchEvent(MotionEvent ev) {......return mDragController.onInterceptTouchEvent(ev);}
代码中省略了与其他功能的不部分代码,最后调用了DragController.onInterceptTouchEvent() ,并取其返回值作为自身方法的返回值。进入DragController.onInterceptTouchEvent()。
/*** Call this from a drag source view.*/public boolean onInterceptTouchEvent(MotionEvent ev) {......final int action = ev.getAction();final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());final int dragLayerX = dragLayerPos[0];final int dragLayerY = dragLayerPos[1];switch (action) {case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_DOWN:// Remember location of down touchmMotionDownX = dragLayerX;mMotionDownY = dragLayerY;mLastDropTarget = null;break;case MotionEvent.ACTION_UP:if (mDragging) {drop(dragLayerX, dragLayerY);}endDrag();break;case MotionEvent.ACTION_CANCEL:cancelDrag();break;}return mDragging;}

这里我们关心的是它的返回值。可以看到方法将mDragging作为返回值。当触发了拖拽状态,在的DragController.startDrag()中将mDragging的值改为true。所以这里也将返回trueDragLayer将拦截MotionEvent,并传给自身的onTouchEvent()方法,在onTouchEvent()中对图标进行移动,刷新界面。


    /*** Call this from a drag source view.*/public boolean onTouchEvent(MotionEvent ev) {......final int action = ev.getAction();final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());final int dragLayerX = dragLayerPos[0];final int dragLayerY = dragLayerPos[1];switch (action) {case MotionEvent.ACTION_DOWN:// Remember where the motion event startedmMotionDownX = dragLayerX;mMotionDownY = dragLayerY;//判断当前的触点是否处于屏幕边缘的ScrollZone,当处于这个区域时//状态mScrollState将转变为SCROLL,并且在一定时间的停留之后,屏幕滑动到另一屏。if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {mScrollState = SCROLL_WAITING_IN_ZONE;mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);} else {mScrollState = SCROLL_OUTSIDE_ZONE;}break;case MotionEvent.ACTION_MOVE://调用handleMoveEvent()处理图标移动handleMoveEvent(dragLayerX, dragLayerY);break;case MotionEvent.ACTION_UP:// Ensure that we've processed a move event at the current pointer location.handleMoveEvent(dragLayerX, dragLayerY);mHandler.removeCallbacks(mScrollRunnable);if (mDragging) {	//根据目前相对DragLayer的坐标,将图标“降落”到指定的DropTarget上。drop(dragLayerX, dragLayerY);}endDrag();break;case MotionEvent.ACTION_CANCEL:cancelDrag();break;}return true;}

onTouchEvent()中处理的事件涉及到不同状态之间的转换,以及每种状态之下对相应的MotionEvent的对策。这里同样,从简单的情况入手:图标拖拽起来后,移动一段距离,在屏幕的另一个位置放下。

首先,当拖拽起图标时,拖拽图标的状态被启动,这就是第一部分所探讨的内容。

然后,移动拖拽的图标。此时触发了MotionEvent.ACTION_MOVE事件,紧接着调用handleMoveEvent()来处理移动。进入handleMoveEvent()来看看图标移动是怎么实现的。


private void handleMoveEvent(int x, int y) {//更新在DragLayer中的位置mDragObject.dragView.move(x, y);// Drop on someone?final int[] coordinates = mCoordinatesTemp;//根据当前的位置寻找DropTarget对象来放置图标DropTarget dropTarget = findDropTarget(x, y, coordinates);mDragObject.x = coordinates[0];mDragObject.y = coordinates[1];if (dropTarget != null) {DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);if (delegate != null) {dropTarget = delegate;}if (mLastDropTarget != dropTarget) {if (mLastDropTarget != null) {//从最后一次记录的DropTarget中退出mLastDropTarget.onDragExit(mDragObject);}//进入到当前寻找到的DropTargetdropTarget.onDragEnter(mDragObject);}dropTarget.onDragOver(mDragObject);} else {if (mLastDropTarget != null) {mLastDropTarget.onDragExit(mDragObject);}}mLastDropTarget = dropTarget;// Scroll, maybe, but not if we're in the delete region.boolean inDeleteRegion = false;if (mDeleteRegion != null) {inDeleteRegion = mDeleteRegion.contains(x, y);}// After a scroll, the touch point will still be in the scroll region.// Rather than scrolling immediately, require a bit of twiddling to scroll againfinal int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();mDistanceSinceScroll +=Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));mLastTouch[0] = x;mLastTouch[1] = y;//判断当前拖拽的图标是否处于ScrollZone即滑动区域。//并且根据在哪个一个ScrollZone来处理屏幕滑动的方向。if (!inDeleteRegion && x < mScrollZone) {if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {mScrollState = SCROLL_WAITING_IN_ZONE;if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {mScrollRunnable.setDirection(SCROLL_LEFT);mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);}}} else if (!inDeleteRegion && x > mScrollView.getWidth() - mScrollZone) {if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {mScrollState = SCROLL_WAITING_IN_ZONE;if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {mScrollRunnable.setDirection(SCROLL_RIGHT);mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);}}} else {if (mScrollState == SCROLL_WAITING_IN_ZONE) {mScrollState = SCROLL_OUTSIDE_ZONE;mScrollRunnable.setDirection(SCROLL_RIGHT);mHandler.removeCallbacks(mScrollRunnable);mDragScroller.onExitScrollArea();}}}

handleMoveEvent()主要处理拖拽过程中需要处理的事务。包括:1、在更新图标在屏幕中的位置,并刷新UI2、判断图标当前所处的位置。包括SCROLL_OUTSIDE_ZONESCROLL_WAITING_IN_ZONE,对处于SCROLL_WAITING_IN_ZONE位置时,需要根据具体的位置,向前或向后切换显示的屏幕。再回到上面假设的情况中。则此时只是简单的刷新了位置信息,并重新绘制图标。


最后,当松开拖拽的对象时,触发了MotionEvent.ACTION_UP事件。则进入下面一段代码:

	    // Ensure that we've processed a move event at the current pointer location.handleMoveEvent(dragLayerX, dragLayerY);mHandler.removeCallbacks(mScrollRunnable);if (mDragging) {drop(dragLayerX, dragLayerY);}endDrag();

先调用handleMoveEvent()确保已经完成了位置移动的操作。接下来调用mHandler.removeCallbacks(mScrollRunnalbe)取消可能存放在消息队列中的滑动任务。接着调用drop(dragLayerX,dragLayerY)将拖拽的对象放置合适的DropTarget对象中(如WorkspaceFolder)。


private void drop(float x, float y) {......//根据当前的坐标查找适合的DropTarget对象final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);......boolean accepted = false;if (dropTarget != null) {mDragObject.dragComplete = true;dropTarget.onDragExit(mDragObject);if (dropTarget.acceptDrop(mDragObject)) {//将拖拽的对象放置到指定的DropTarget对象中。dropTarget.onDrop(mDragObject);accepted = true;}}mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted);}

最后调用endDrag()结束拖拽过程。

private void endDrag() {if (mDragging) {//拖拽结束mDragging = false;for (DragListener listener : mListeners) {//调用回调方法,通知拖拽结束。listener.onDragEnd();}if (mDragObject.dragView != null) {//不需要DragView了,将其删除mDragObject.dragView.remove();mDragObject.dragView = null;}}}
至此,拖拽的图标的过程就结束了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/40336.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android聊天界面实现方式

最近心血来潮&#xff0c;打算实现一个很久之前就想实现的一个界面&#xff0c;就是聊天界面&#xff0c;当时觉得好高大上啊&#xff0c;完全不会啊&#xff0c;不过最近不小心找到了方法。 效果图 本来是想上传一张动态图的&#xff0c;但是不知道怎么回事&#xff0c;半天传…

Android安卓-开发一个android桌面

从0开始 开发一个属于自己的桌面程序 最近在开发一个新项目&#xff0c;需要把应用改成桌面&#xff0c;并引导用户设置为默认桌面&#xff0c;完成后的效果如下图&#xff1a; 1.添加XML <activity android:name".activitys.DeskTop" android:launchMode"si…

安卓11客制需求:<MtkSettings:添加桌面设置,并且能够正常切换桌面APK>

需要修改的路径代码路径&#xff1a;vendor/mediatek/proprietary/packages/apps/MtkSettings/AndroidManifest.xml <!-- 注册一个activity&#xff0c;用于启动 com.android.internal.app.ResolverActivity --><!-- gyh add --><activity android:name".S…

还在用手记录会议笔记?录音转文字简单的方法介绍

相信很多小伙伴们的单位或者是公司每周都要开会&#xff0c;开会一般都要写会议记录存档&#xff0c;有很多朋友这个时候在会议上拼命的记&#xff0c;但是对于一些打字慢的或是写字慢的人来说怎么办呢&#xff1f;这样就会跟不上&#xff0c;其实有更搞笑的方法&#xff0c;首…

只需一键录音转文字,会议记录让你不在烦恼

来让我看看还有哪位朋友&#xff0c;还在傻傻一遍遍听会议录音&#xff0c;手写会议纪要呀&#xff1f; 都2022年了&#xff0c;你不会还不知道这款只需一键就能语音转文字的APP吧&#xff01; 90后打工人&#xff0c;今天给大家分享个职场必备软件&#xff0c;让你瞬间效率拉…

3、微信小程序-通信

文章目录 前言一、组件通信1.微信小程序组件内部&#xff08;页面与逻辑层之间&#xff09;通信2.微信小程序组件之间&#xff08;父子组件&#xff09;通信&#xff08;1&#xff09;父组件是如何向子组件传递数据的&#xff08;2&#xff09;子组件是如何向父组件传递数据的 …

微信小程序之页面通信方式

文章目录 一、前言二、页面通信是什么&#xff1f;三、传值的几种常用方式1.页面跳转传参2.页面跳转传入数据3.使用全局变量传递数据4.利用缓存进行传值 四、小结 一、前言 提示&#xff1a;微信小程序中&#xff0c;页面间的通信方式很重要&#xff0c;通信方式也有很多种&am…

【最新版全插件】多功能同城优选小程序源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 1.为本地的线下商家提供线上销售渠道。一直以来本地商品、娱乐、休闲、旅游服务线上购买大家都是以美团为准。近几年来随着微信公众号、小程序的渗透力逐渐加强&#xff0c;越来越多的…

微信公众号开发之绑定微信开发者

第一步&#xff1a;登录微信公众号&#xff0c;绑定网页开发者 在登录后的界面中&#xff0c;我们向下拉在左侧会看到有一个“开发者工具”点击。 这时在开发者工具中&#xff0c;会看到有好几个工具&#xff0c;其中有一个“web开发者工具”&#xff0c;我们点击进入。 在这里…

微信公众号登录授权(全网发布)一键绑定公众号设置

微信公众号登录授权&#xff08;全网发布&#xff09;一键绑定公众号设置教程及常见问题 前言&#xff1a;本操作跟服务器配置&#xff0c;网络环境等综合环境有关&#xff08;95%&#xff09;&#xff0c;另外存在5%的运气和人品。WeiDogs官方确保程序此功能正常并提供设置教程…

python微信公众号微信用户绑定第三方网站

场景 例如,某用户在第三方购物平台的账号(user_id/手机号码等)需要跟该用户的微信账号进行绑定, 实现在该购物平台的微信公众号中查询个人信息, 消费记录, 充值记录等操作.总的来说便是,将微信用户的open_id跟第三方网址user_id/手机号码等进行一对一关联 实现方案 微信用户…

微信公众号开发—通过网页授权实现业务系统登录及用户绑定(微信网页授权自动登录业务系统)

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; 微信公众号开发—通过网页授权实现业务系统登录及用户绑定(微信网页授权…

微信授权登录流程以及公众号配置方法(golang后端)

一、准备一个已经认证OK的微信公众号和已经备案的域名&#xff0c;且解析好配置好https证书。 1.如上图 微信公众号 > 基本配置 &#xff0c;设置开发者密码 2.设置IP白名单&#xff0c;白名单填写提供后端服务的服务器公网IP 二、公众号服务器配置。 1.找到基本配置 2.将服…

微信公众号用户与网站用户的绑定方案

现在很多网站都已经建立了一套完整的用户账号体系&#xff0c;基于这套体系&#xff0c;再做其他应用的用户扩展就非常方便。例如&#xff0c;有了微软的outlook账户&#xff0c;就可以登录win8&#xff0c;可以登录微软的邮箱&#xff0c;还可以登录skype。同样地&#xff0c;…

微信开放平台 帐号管理 绑定在同一个开放平台帐号下的公众号及小程序让用户unionid一致...

开发十年&#xff0c;就只剩下这套架构体系了&#xff01; >>> 第三方平台在获得此权限后&#xff0c;可以代替已授权的公众号/小程序创建开放平台帐号或进行绑定/解绑操作。 绑定在同一个开放平台帐号下的公众号及小程序&#xff0c;用户unionid一致。开发者可调用…

wechat-0051,微信公众号,第三方登录—扫码绑定

声明&#xff1a;这边所谓的第三方登录&#xff0c;不是通过网页授权的方式&#xff0c;而是利用生成带参数的二维码&#xff0c;将微信号和平台账号绑定实现。如果你要做的是授权登录&#xff0c;请查看 https://blog.csdn.net/wrongyao/article/details/80229986 微信第三方…

IT创业项目 - 跟淘宝商城合作网赚项目,赚多少你说了算!

【项目介绍】&#xff1a; 使用淘宝商城的巨大流量销售产品或者服务。 【市场前景】&#xff1a; 淘宝网作为最大的B2C交易平台,淘宝商城又是淘宝网要点的发展对象&#xff0c;越来越多的年轻人喜爱在淘宝购买产品和服务。使用好淘宝的流量&#xff0c;赚钱十分简略。其实这是一…

微信公众号开发消息推送以及图文推送

今天给大家分享的关注公众号自动推送图文消息&#xff0c;以及做一个超牛逼的机器人。 先看看效果。 发错图了。。。这是我昨天开发的一款机器人chu了会骂人啥都不会了。我今天将它词库进行了更新和升级&#xff0c;接入了http://www.itpk.cn/ 机器人第三词库 先给你截图&…

【全网最简单】给朋友- 制作,微信公众号推送教程

简介&#xff1a; 前段时间&#xff0c;抖音非常火的微信公众号推送天气&#xff0c;生日&#xff0c;祝福等信息给女朋友专属推送&#xff0c;而且大部分都是Python写的&#xff0c;对于我来说&#xff0c;必须得整起&#xff0c;上java版本&#xff0c;到时候打个包&#xff…

小扎All in AIGC,连夜成立顶级产品团队

金磊 发自 凹非寺量子位 | 公众号 QbitAI ChatGPT的火爆&#xff0c;终究是让Meta坐不住了。 这不&#xff0c;小扎&#xff08;扎克伯格&#xff09;连夜对外宣布了公司的大动作&#xff1a; 成立顶级产品团队&#xff0c;专注AIGC。 这个团队可以说是整合了全公司搞AIGC的人才…