Android 項目:畫圖白板APP開發(六)——分頁展示

????????本篇將介紹如何為我們的畫板應用添加分頁展示功能,讓用戶可以創建多個畫布并在它們之間輕松切換。這章沒有啥知識點的講解,主要介紹一下每頁保存的數據結構是什么樣的。

一、ListView

????????多頁數據的管理我們使用ListView。之前有文章講過ListView這里就不多贅述了,感興趣的讀者可以看看。Android最常用的控件ListView(詳解)?。

直接上圖例和代碼:

//綁定適配器(傳入handler)
adapter = new PictureAdapter(mContext,R.layout.list_item,listDate,handler);
viewMember.lv_tables.setAdapter(adapter);

(1)PictureView.java

//保存某一頁的視圖信息
public class PictureView {//保存比例信息Matrix matrixMain = new Matrix();//保存撤銷和恢復的信息private ArrayList<PaintDates> paintedList = new ArrayList<>();public ArrayList<MessageStrokes> getCancelList() {return cancelList;}public void setCancelList(ArrayList<MessageStrokes> cancelList) {this.cancelList = cancelList;}public ArrayList<MessageStrokes> getRecoverList() {return recoverList;}public void setRecoverList(ArrayList<MessageStrokes> recoverList) {this.recoverList = recoverList;}private ArrayList<MessageStrokes> cancelList = new ArrayList<>();private ArrayList<MessageStrokes> recoverList = new ArrayList<>();//設置一個專門為撤銷,回退服務的list//用來保存每一個操作的意義(可能是單筆的,可能是多筆)//view上private Bitmap cacheBitmap;private Canvas cacheCanvas ;public PictureView(int width, int height) {cacheBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_4444);cacheCanvas = new Canvas(cacheBitmap);}public ArrayList<PaintDates> getPaintedList() {return paintedList;}public void setPaintedList(ArrayList<PaintDates> paintedList) {this.paintedList = paintedList;}public Bitmap getCacheBitmap() {return cacheBitmap;}public void setCacheBitmap(Bitmap cacheBitmap) {this.cacheBitmap = cacheBitmap;}public Canvas getCacheCanvas() {return cacheCanvas;}public void setCacheCanvas(Canvas cacheCanvas) {this.cacheCanvas = cacheCanvas;}}

PictureView?是一個數據模型類,用于保存畫板中某一頁的完整狀態信息。

  • (cacheBitmap?和?cacheCanvas):保存當前頁面的最終渲染結果
  • paintedList:存儲所有的筆畫數據

  • cancelList存儲已執行但可撤銷的操作

  • recoverList存儲已撤銷但可恢復的操作

  • matrixMain保存縮放、平移、旋轉等變換信息

(2)PictureAdapter.java

//適配器
public class PictureAdapter extends ArrayAdapter<PictureView> {//用來判斷當前View上顯示的時哪個(默認為第一個)public int localNum = 1;private Handler handler;public PictureAdapter(@NonNull Context context, int resource, @NonNull List<PictureView> objects, Handler handler) {super(context, resource, objects);this.handler = handler;}@SuppressLint("SetTextI18n")@NonNull@Overridepublic View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {PictureView p = getItem(position);View view;//新增一個內部類 ViewHolder,用于對控件的實例進行緩存ViewHolder viewHolder;if (convertView==null){//為每一個子項加載設定的布局view= LayoutInflater.from(getContext()).inflate(R.layout.list_item,parent,false);viewHolder= new ViewHolder();//分別獲取 imageview 和 textview 的實例viewHolder.image =view.findViewById(R.id.iv_image);viewHolder.imageNum =view.findViewById(R.id.tv_num);viewHolder.imageDelete=view.findViewById(R.id.bt_delete_item);viewHolder.layout = view.findViewById(R.id.fl_item);view.setTag(viewHolder);//將 viewHolder 存儲在 view 中}else {view=convertView;viewHolder= (ViewHolder) view.getTag();//重新獲取 viewHolder}// 設置要顯示的內容viewHolder.image.setImageBitmap(p.getCacheBitmap());viewHolder.imageNum.setText((position+1)+"");if((position+1)==localNum){//008FFBviewHolder.imageNum.setTextColor(Color.parseColor("#008FFB"));}else {viewHolder.imageNum.setTextColor(Color.WHITE);}//按鈕點擊事件(使用handler)viewHolder.imageDelete.setOnClickListener(v->{//創建一個線程Thread t = new Thread(() -> {Message m = handler.obtainMessage();m.what = 0x101;m.arg1 = position;handler.sendMessage(m);});t.start();});viewHolder.layout.setOnClickListener(v->{Thread t =new Thread(() -> {Message m = handler.obtainMessage();m.what = 0x102;m.arg1 = position;handler.sendMessage(m);});t.start();});return view;}private static class ViewHolder {TextView imageNum;ImageView image;ImageButton imageDelete;FrameLayout layout;}}

PictureAdapter中的點擊事件,通過Handler傳遞

(3)Handler

@SuppressLint("HandlerLeak")
private void initHandler() {handler = new Handler(Looper.getMainLooper()){@SuppressLint("SetTextI18n")@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case 0x101://刪除//彈出提升框now = msg.arg1;new AlertDialog.Builder(mContext).setTitle("提示").setMessage("確定要刪除 "+ (now+1) +"號視圖嗎?").setPositiveButton("確定", (dialogInterface, i) -> {//是否為刪除的為當前顯示的視圖System.out.println("AAAAAAAAAA:   "+ (now+1) +" "+NowNum);if((now+1)==NowNum&&now==0){if(total == 1){Toast.makeText(mContext, "您無法刪除最后一個視圖", Toast.LENGTH_SHORT).show();}else {total--;//清空內存clearBitmap(listDate.get(now));listDate.remove(now);adapter.localNum = NowNum;blackboardView1.updateView(NowNum-1);}}else if((now+1)==NowNum&&now!=0){//向上移動total--;NowNum--;clearBitmap(listDate.get(now));listDate.remove(now);adapter.localNum = NowNum;blackboardView1.updateView(NowNum-1);}else if((now+1)!=NowNum&&(now+1)>NowNum){//不動total--;clearBitmap(listDate.get(now));listDate.remove(now);}else {//整體上移total--;NowNum--;clearBitmap(listDate.get(now));listDate.remove(now);adapter.localNum = NowNum;blackboardView1.updateView(NowNum-1);}viewMember.tv_whereForNum.setText(numToString(NowNum)+"/"+numToString(total));adapter.notifyDataSetChanged();}).setNegativeButton("取消",null).show();break;case 0x102://點擊試圖切換now = msg.arg1;NowNum = now+1;adapter.localNum = NowNum;//發送消息進行上傳(目前感覺沒必要上傳)
//                        Message message = new Message();
//                        message.what = 16;
//                        message.arg1 = NowNum;
//                        System.out.println("popopopo nowNum:"+NowNum);
//                        //operateHandler//同時更新的還有底部的數字viewMember.tv_whereForNum.setText(numToString(NowNum)+"/"+numToString(total));
//                        oldBitmap = blackboardView1.cacheBitmap;
//                        operateHandler.sendEmptyMessage(100);//通知截屏上傳(在沒更新之前)blackboardView1.updateView(NowNum-1);adapter.notifyDataSetChanged();break;case 0x103://漫游:顯示比例數值viewMember.tv_zoomNum.setText(FTOString((Float) msg.obj));break;case 0x104://down的是時候不讓獲取獲取焦點viewMember.bt_tables.setEnabled(false);viewMember.bt_last.setEnabled(false);viewMember.bt_next.setEnabled(false);viewMember.bt_add.setEnabled(false);viewMember.tv_whereForNum.setEnabled(false);Resources resources_table = mContext.getResources();if (viewMember.lv_tables.getVisibility() == View.VISIBLE) {viewMember.lv_tables.setVisibility(View.GONE);viewMember.tv_whereForNum.setTextColor(Color.WHITE);}if(viewMember.ll_more.getVisibility()==View.VISIBLE){viewMember.ll_more.setVisibility(View.GONE);Drawable imageDrawable = resources_table.getDrawable(R.drawable.tables_uncheck);viewMember.bt_tables.setBackground(imageDrawable);}//開始工具類的按鈕//1.首先要讓下面一排子的東西點不了viewMember.bt_pen.setEnabled(false);viewMember.bt_eraser.setEnabled(false);viewMember.bt_revoke.setEnabled(false);viewMember.bt_recover.setEnabled(false);viewMember.bt_zoom.setEnabled(false);//2.布局恢復if(viewMember.ll_penWidth.getVisibility() == View.VISIBLE){//這個就證明在畫筆的行列viewMember.bt_width_1.setEnabled(false);viewMember.bt_width_2.setEnabled(false);viewMember.bt_width_3.setEnabled(false);viewMember.bt_width_4.setEnabled(false);viewMember.bt_width_5.setEnabled(false);viewMember.bt_penColor.setEnabled(false);if(viewMember.ll_colorAndAlpha.getVisibility() == View.VISIBLE){viewMember.ll_colorAndAlpha.setVisibility(View.GONE);}}if(viewMember.ll_eraser.getVisibility() == View.VISIBLE){viewMember.bt_son_eraser.setEnabled(false);viewMember.bt_handwriting_eraser.setEnabled(false);viewMember.sb_clear_sliding.setEnabled(false);}break;case 0x105://up的時候解封viewMember.bt_tables.setEnabled(true);viewMember.bt_last.setEnabled(true);viewMember.bt_next.setEnabled(true);viewMember.bt_add.setEnabled(true);viewMember.tv_whereForNum.setEnabled(true);viewMember.bt_pen.setEnabled(true);viewMember.bt_eraser.setEnabled(true);viewMember.bt_revoke.setEnabled(true);viewMember.bt_recover.setEnabled(true);viewMember.bt_zoom.setEnabled(true);if(viewMember.ll_penWidth.getVisibility() == View.VISIBLE){//這個就證明在畫筆的行列viewMember.bt_width_1.setEnabled(true);viewMember.bt_width_2.setEnabled(true);viewMember.bt_width_3.setEnabled(true);viewMember.bt_width_4.setEnabled(true);viewMember.bt_width_5.setEnabled(true);viewMember.bt_penColor.setEnabled(true);}if(viewMember.ll_eraser.getVisibility() == View.VISIBLE){viewMember.bt_son_eraser.setEnabled(true);viewMember.bt_handwriting_eraser.setEnabled(true);viewMember.sb_clear_sliding.setEnabled(true);}break;case 0x106: //電子筆清除屏幕new AlertDialog.Builder(mContext).setTitle("提示").setMessage("確定要清屏嗎?").setPositiveButton("確定", (dialogInterface, i) -> {blackboardView1.clear();blackboardView1.isDialog = false;}).setNegativeButton("取消",(dialogInterface, i) -> {blackboardView1.isDialog = false;}).show().setCanceledOnTouchOutside (false);blackboardView1.clear_hardware();//清除其他筆畫}super.handleMessage(msg);}};
}

Handler 涉及到了功能:這里涉及到后面要講的功能,這里簡單說下

  • 0x101 :當ListView點擊刪除時調用,彈出 AlertDialog 要求用戶確定操作。

  • 0x102 :點擊切換視圖,主界面顯示對應頁的畫布。

  • 0x103 :放大縮小時,顯示比例數值。比如50% , 300%。

  • 0x104 :用戶畫線的時候,不允許操作ListView。

  • 0x105 :沒有寫畫時允許操作。

  • 0x106:電子筆點擊按鈕后,調用清屏功能。

(4)更新畫布 updateView

//切換視圖,刷新
public void updateView(int whereView){//對所有數據進行更新ViewNum = whereView;mPaintedList = mListDate.get(ViewNum).getPaintedList();mCancelList = mListDate.get(ViewNum).getCancelList();mRecoverList = mListDate.get(ViewNum).getRecoverList();cacheBitmap = mListDate.get(ViewNum).getCacheBitmap();cacheCanvas = mListDate.get(ViewNum).getCacheCanvas();mMatrixMain = mListDate.get(ViewNum).matrixMain;//傳一下handlermMatrixMain.getValues(mainDate);Message m = this.handler.obtainMessage();m.what = 0x103;m.obj = mainDate[0];this.handler.sendMessage(m);cacheCanvas.drawColor(0,PorterDuff.Mode.CLEAR);bottomCanvas.drawColor(0,PorterDuff.Mode.CLEAR);invalidateReason = REASON_RE;invalidate();
}

將 PictureView 中的對象賦值給當前視圖即可。

二、PictureView中使用的實體類

這里具體介紹一下 PaintDates 和 MessageStrokes 具體內容。

(1)PaintDates.java

//保存每一筆的情況
//之后要實現筆鋒效果(保存的就不是paint和path了)
public class PaintDates {//沒必要每次都new一個Paint:就透明的與width不同Paint mPaint;Path mPath; //專門為透明度服務List<PathAndWidth> mOnePaths ;//保存每一筆畫的偏移ArrayList<Matrix> mMatrixS = new ArrayList<>();//設置一個model來判斷是這個類是點還是線(經歷了move就是線,沒有就是點)private int lineModel = 1;//首先開始為點final int POINT = 1;//點final int LINE = 2 ;//線final int DOTTED_LINE = 3;//虛線//保存起點的x,y;float mx;float my;float mXToMatrix;float mYToMatrix;//初始寬度(為點和虛線提供)float mWidth;//是否為待刪除狀態(為筆畫刪除提供服務)private boolean isDelete = false;private boolean isCut = false;//設置一個與他同病相憐的兄弟集合(說白了保存id)public boolean isCut() {return isCut;}public void setCut(boolean cut) {isCut = cut;}public boolean isDelete() {return isDelete;}public void setDelete(boolean delete) {isDelete = delete;}public PaintDates(Paint paint, List<PathAndWidth> path, float x, float y,float width) {mPaint = paint;mOnePaths = path;mx = x;my = y;mXToMatrix = x;mYToMatrix = y;mWidth = width;}public PaintDates(PaintDates pd){mPaint = pd.mPaint;mPath = pd.mPath;mOnePaths = new ArrayList<>();for (int i = 0; i < pd.mOnePaths.size() ; i++) {mOnePaths.add(new PathAndWidth(pd.mOnePaths.get(i)));}for (int i = 0; i <pd.mMatrixS.size() ; i++) {mMatrixS.add(new Matrix(pd.mMatrixS.get(i)));}mx = pd.mx;my = pd.my;mXToMatrix = pd.mXToMatrix;mYToMatrix = pd.mYToMatrix;mWidth = pd.mWidth;lineModel = pd.getLineModel();}public void draw(Canvas canvas){if(lineModel == POINT){drawPoint(canvas);}else if(lineModel == LINE){drawLine(canvas);}else {drawDottedLine(canvas);}}private void drawDottedLine(Canvas canvas) {canvas.drawPath(mPath,mPaint);}//實現包裹效果//為了實現有筆峰的包裹效果,應該使用一個公式:前一個線的寬度=m,當前線的寬度=n -> m/n + 1.public void drawPlus(Canvas canvas){Paint coverPaint = new Paint(mPaint);int A = coverPaint.getAlpha();coverPaint.setColor(Color.parseColor("#000000"));  //先暫時換個黑色coverPaint.setAlpha(A);if(lineModel == POINT){//點的高光操作coverPaint.setStrokeWidth(mWidth*2f);canvas.drawPoint(mx,my,coverPaint);}else if(lineModel == LINE){for (PathAndWidth mPath:mOnePaths) {coverPaint.setStrokeWidth(mPath.width*2f);canvas.drawPath(mPath.path,coverPaint);if(mPath.addPaths!=null){for (int i = 0; i <mPath.addPaths.size() ; i++) {//coverPaint.setStrokeWidth(mPath.width*3f);canvas.drawPath(mPath.addPaths.get(i),coverPaint);}}}}else {//虛線包裹coverPaint.setStrokeWidth(mWidth*2f);canvas.drawPath(mPath,coverPaint);}}//實現橡皮擦的高光效果public void drawDottingRed(Canvas canvas,float p){Paint dotRedPaint = new Paint(mPaint);dotRedPaint.setStrokeCap(Paint.Cap.BUTT);dotRedPaint.setStrokeJoin(Paint.Join.BEVEL);PathEffect effect = new DashPathEffect(new float[]{40f,20f,10f,20f},p);dotRedPaint.setXfermode(null);dotRedPaint.setPathEffect(effect);dotRedPaint.setColor(Color.RED);dotRedPaint.setAlpha(100);canvas.drawPath(mPath,dotRedPaint);}//畫點的邏輯public void drawPoint(Canvas canvas){mPaint.setStrokeWidth(mWidth);canvas.drawPoint(mx,my,mPaint);}//畫線的邏輯public void drawLine(Canvas canvas){for (PathAndWidth mPath:mOnePaths) {mPaint.setStrokeWidth(mPath.width);if(mPath.addPaths != null){
//                Paint RedPaint = new Paint(mPaint);
//                RedPaint.setColor(Color.RED);for (int i = 0; i <mPath.addPaths.size() ; i++) {canvas.drawPath(mPath.addPaths.get(i),mPaint);}}canvas.drawPath(mPath.path,mPaint);}}//畫最后一段即可public void drawPatch(Canvas canvas){PathAndWidth pw = mOnePaths.get(mOnePaths.size()-1);mPaint.setStrokeWidth(pw.width);canvas.drawPath(pw.path,mPaint);if(pw.addPaths!=null){for (int i = 0; i < pw.addPaths.size(); i++) {//canvas.drawPath(pw.addPaths.get(i),mPaint);canvas.drawPath(pw.addPaths.get(i),mPaint);}}}//添加前面一段路徑的筆鋒pathpublic void drawFrontAddPath(Canvas canvas){PathAndWidth pw = mOnePaths.get(mOnePaths.size()-2);mPaint.setStrokeWidth(pw.width);if(pw.addPaths!=null){for (int i = 0; i < pw.addPaths.size(); i++) {canvas.drawPath(pw.addPaths.get(i),mPaint);}}}public static class PathAndWidth{Path path;//添加的pathArrayList<Path> addPaths;//這個path已經無法滿足需求Float width ;//形成path的后一個點float x;float y;//此點的變形float xToMatrix;float yToMatrix;//判斷這個線是否要分割boolean isCut = false;//比例float BL = -1;public PathAndWidth(PathAndWidth paw){if(paw.path!=null){path = new Path(paw.path);width = paw.width;}x = paw.x;y = paw.y;xToMatrix = paw.xToMatrix;yToMatrix = paw.yToMatrix;addPaths = paw.addPaths;}public PathAndWidth(Path path, Float width,float x,float y) {this.path = path;this.width = width;this.x = x;this.y = y;//在沒有zoom的情況下與原始點相同xToMatrix = x;yToMatrix = y;}//這是為透明度服務的public PathAndWidth(float x,float y){this.x = x;this.y = y;//在沒有zoom的情況下與原始點相同xToMatrix = x;yToMatrix = y;}}public int getLineModel() {return lineModel;}public void setLineModel(int lineModel) {this.lineModel = lineModel;}//顏色變化選項(后續有要求在搞)}//思路1:每兩個點之間保存一段路徑(性能要求非常高)//思路2:保存點的信息化橢圓(需要保存一個方形)

核心成員變量及其作用:

變量名類型作用
mPaintPaint保存繪制這一筆時所用的畫筆樣式(顏色、透明度、抗鋸齒等)
mOnePathsList<PathAndWidth>這是最關鍵的數據。它保存了構成這一筆的所有筆觸段PathAndWidth?對象)。每個筆觸段都包含一小段路徑 (Path) 和繪制該段路徑時動態變化的筆觸寬度,以此來實現筆鋒效果(壓感、速度感應)。
mMatrixSArrayList<Matrix>保存這一筆畫所經歷過的所有變換矩陣(如平移、縮放、旋轉)。這使得該筆畫能夠跟隨畫布進行變換,而自身的原始數據保持不變。
lineModelint標識這一筆的類型POINT?(一個點)、LINE?(一條連續的線)、DOTTED_LINE?(一條虛線)。繪制和擦除邏輯會根據不同類型而變化。
mx, myfloat記錄這一筆的起始點坐標。對于POINT類型,這就是點的位置;對于LINE,這是moveTo的起點。
mWidthfloat記錄這一筆的初始(或基礎)寬度。主要用于繪制POINTDOTTED_LINE,因為LINE的寬度由mOnePaths中的每個PathAndWidth動態管理。
isDelete,?isCutboolean狀態標志。用于實現筆畫刪除筆畫分割功能。前面橡皮擦那章解釋過

核心方法及其作用:

方法名作用
draw(Canvas canvas)核心繪制方法。根據lineModel調用對應的繪制方法(drawPoint,?drawLine,?drawDottedLine),將這一筆畫到傳入的Canvas上。
drawPlus(Canvas canvas)繪制包裹高光效果。通常用于實現筆畫選中狀態。它會用原筆畫兩倍的寬度和特定顏色(代碼中為黑色)再畫一遍,形成“包裹”或“高亮”效果,提示用戶該筆畫被選中。
drawDottingRed(Canvas canvas, float p)繪制虛線效果。用于橡皮擦功能。當用戶使用橡皮擦時,可能用紅色的虛線來預覽即將被擦除的筆畫區域。參數p用于控制虛線模式的偏移,實現動畫效果。
drawPatch(Canvas canvas)僅繪制最后一小段路徑。用于實時繪制(即用戶手指還在移動時)。為了提高性能,在用戶快速繪畫時,不需要重繪整個復雜路徑,只需繪制最新的一小段(mOnePaths的最后一個元素)。
drawFrontAddPath(...)繪制前一段路徑的筆鋒。這是一個更細粒度的優化,用于確保在連續繪制時,筆鋒的銜接部分也能被正確繪制,避免出現斷點。

PathAndWidth (路徑與寬度)

這是一個內部靜態類,是?PaintDates?的組成部分。它可以被稱為筆觸段數據持有者。它的存在是實現筆鋒效果的關鍵。

核心成員變量及其作用:

變量名類型作用
pathPath保存一小段貝塞爾曲線路徑(由?quadTo生成)。
widthFloat保存繪制這一小段路徑時所用的筆觸寬度。筆鋒效果就是通過路徑不斷變化的同時,寬度也隨之變化(模擬壓感)來實現的。
addPathsArrayList<Path>附加路徑。為了實現筆鋒效果,前幾章有介紹
x, yfloat記錄這一小段路徑的終點坐標
xToMatrix, yToMatrixfloat記錄經過變換矩陣作用后,終點坐標應該所在的位置。用于坐標轉換計算。
isCutboolean標識此筆觸段是否處于被分割的狀態。

(2)MessageStrokes.java

//負責保存每一個操作
public class MessageStrokes {int MassageType;  //信息種類ArrayList<IdAndStrokes> paintStrokes;//保存每個筆畫的Matrix matrix;Matrix mainMatrix;//用于保存右側的數字public MessageStrokes(int massageType) {MassageType = massageType;}static class IdAndStrokes{int id ;int num ;//針對于橡皮擦單獨設置,用來判斷需要刪除此ID幾次。PaintDates pd ;public IdAndStrokes(int id,PaintDates pd) {this.id = id;this.pd = pd;}}
}
//對于筆畫刪除而言,一定是倒著刪除。所以恢復的時候一定是正著來(id+筆畫)

這個類的主要作用是:封裝并保存一個完整的用戶操作,用于實現撤銷 (Undo) 和重做 (Redo) 功能,后面介紹撤銷恢復時詳細說明。本章節篇幅較少,主要是介紹多畫布的框架,為后面的章節打好基礎。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/922106.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/922106.shtml
英文地址,請注明出處:http://en.pswp.cn/news/922106.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

智能眼鏡產品成熟度分析框架與評估

引言 當前(2025年9月12日),智能眼鏡(Smart Glasses)市場正處于快速演進階段,從早期的新奇設備向主流消費電子轉型。AI整合、AR顯示和多模態交互的進步推動了這一轉變。根據最新數據,2025年AI眼鏡發貨量預計達686萬臺,同比增長265%,全球市場規模從2024年的約19.3億美元…

(網絡編程)網絡編程套接字 UDP的socket API 代碼解析

網絡編程基礎 為什么需要網絡編程?--豐富的網絡資源 用戶在瀏覽器中,打開在線視頻網站,如優酷看視頻,實質是通過網絡,獲取到網絡上的一個視頻資源。與本地打開視頻文件類似,只是視頻文件這個資源的來源是網絡。 相比本地資源來說,網絡提供了更為豐富的網絡資源:所謂的網絡資源…

【STM32】狀態機(State Machine)

這篇博客介紹 狀態機&#xff08;State Machine&#xff09;&#xff0c;適合用于嵌入式開發、驅動開發、協議解析、按鍵識別等多種場景。 一、什么是狀態機&#xff08;State Machine&#xff09;&#xff1f; 狀態機&#xff08;State Machine&#xff09;是一種用于描述系統…

深度學習在離崗檢測中的應用

離崗檢測技術正逐步成為現代企業精細化管理和安全生產的重要工具。這項基于計算機視覺和人工智能的應用&#xff0c;通過自動化、實時化的監測方式&#xff0c;有效提升了工作紀律性和運營效率&#xff0c;為項目管理者和企業提供了創新的監管解決方案。在許多工作場景中&#…

Spring緩存(二):解決緩存雪崩、擊穿、穿透問題

1. 緩存穿透問題與解決方案 1.1 什么是緩存穿透 緩存穿透是指查詢一個不存在的數據&#xff0c;由于緩存中沒有這個數據&#xff0c;每次請求都會直接打到數據庫。 如果有惡意用戶不斷請求不存在的數據&#xff0c;就會給數據庫帶來巨大壓力。 這種情況下&#xff0c;緩存失去了…

PHP 與 WebAssembly 的 “天然隔閡”

WebAssembly&#xff08;簡稱 WASM&#xff09;是一種低級二進制指令格式&#xff0c;旨在為高級語言提供高性能的編譯目標&#xff0c;尤其在瀏覽器環境中實現接近原生的執行效率。它主要用于前端性能密集型場景&#xff08;如游戲引擎、視頻編解碼、3D 渲染等&#xff09;&am…

unity中通過拖拽,自定義scroll view中子物體順序

1.在每個content的子物體上掛載DragHandler腳本&#xff0c;并且添加Canvs Group組件&#xff0c;設置見圖2.DragHandler腳本內容&#xff1a;using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using System.Collections.Generic; using System.Coll…

用 Matplotlib 繪制餅圖:從基礎語法到實戰美化,全面掌握分類數據可視化技巧

用 Matplotlib 繪制餅圖:從基礎語法到實戰美化,全面掌握分類數據可視化技巧 在數據分析與可視化的世界里,**“圖勝千言”**早已成為共識。而在眾多圖表類型中,餅圖(Pie Chart)以其直觀的比例展示方式,成為展示分類數據分布的常見選擇。無論是業務報表、用戶畫像,還是市…

基礎算法之二分算法 --- 2

大家好&#xff0c;不同的時間&#xff0c;相同的地點&#xff0c;時隔多日我們又見面了。繼上次的二分算法后&#xff0c;我們這次要來學習的是二分答案了。這個部分相較于前面的二分算法難度有相當的提升&#xff0c;希望大家有所準備。雖然難度增加了&#xff0c;但是博主還…

發揮nano banana的最大能力

1. 概述Nano Banana 簡介&#xff1a;Nano Banana 是 Google DeepMind 開發的 AI 圖像生成與編輯模型&#xff0c;集成在 Google Gemini 平臺中&#xff08;具體為 Gemini 2.5 Flash 版本&#xff09;。它以高效的圖像編輯能力聞名&#xff0c;尤其在角色一致性、光影理解和快速…

leetcode 面試題01.02判定是否互為字符重排

一、問題描述二、解題思路解法一&#xff1a;對s1和s2進行sort排序&#xff0c;返回s1是否等于s2&#xff1b;解法二&#xff1a;用哈希表分別來記錄s1和s2中字符出現的次數&#xff0c;統計完后&#xff0c;判斷兩個哈希表是否相等;三、代碼實現解法一&#xff1a;時間復雜度&…

Python Yolo8 物體識別

支持單張圖片/圖片目錄批量預標注 默認使用cuda GPU .env HTTP_PROXYhttp://192.168.2.109:10808 HTTPS_PROXYhttp://192.168.2.109:10808pyproject.toml [project] name "yolo-test" version "0.1.0" description "Add your description here&quo…

LeetCode100-234回文鏈表

本文基于各個大佬的文章上點關注下點贊&#xff0c;明天一定更燦爛&#xff01;前言Python基礎好像會了又好像沒會&#xff0c;所有我直接開始刷leetcode一邊抄樣例代碼一邊學習吧。本系列文章用來記錄學習中的思考&#xff0c;寫給自己看的&#xff0c;也歡迎大家在評論區指導…

BUG排查流程

引言簡述Bug排查的重要性分享個人或團隊在Bug排查中的常見挑戰引出日記形式記錄的價值日記格式設計時間戳&#xff1a;記錄問題發現和解決的時間節點問題描述&#xff1a;清晰定義Bug的現象和影響范圍環境信息&#xff1a;操作系統、版本號、依賴庫等關鍵配置復現步驟&#xff…

汽車功能安全 Functional Safety ISO 26262 測試之一

汽車電子電氣系統的日益復雜使得功能安全成為保障車輛可靠性和駕乘安全的關鍵。 本文將圍繞ISO 26262標準的核心內容展開&#xff0c;幫助大家理解如何通過系統化的方法控制風險&#xff0c;進行測試&#xff0c;確保產品安全。 01 什么是功能安全&#xff1f; 首先&#xff0c…

人形機器人賽道的隱形勝負手:低延遲視頻鏈路如何決定機器人未來

一、引言&#xff1a;爆發前夜的人形機器人賽道 2025 年&#xff0c;被業內稱為“人形機器人量產元年”。政策與資本的合力&#xff0c;讓這條原本還帶著科幻色彩的產業賽道&#xff0c;驟然進入現實加速期。國家層面&#xff0c;《“機器人”行動計劃》明確提出要推動人形機器…

從iPhone 17取消SIM卡槽,看企業如何告別“數據孤島”

9月10日&#xff0c;蘋果公司如期召開秋季新品發布會&#xff0c;正式推出iPhone 17系列。除了性能和拍照的常規升級&#xff0c;一個看似不起眼但意義深遠的改變引起了廣泛關注——iPhone 17 Pro系列全面取消了實體SIM卡槽&#xff0c;只保留了eSIM功能。這一舉動不僅僅是技術…

【JavaWeb01】Web介紹

文章目錄1.導學2.Web開發介紹2.1 Web網站的工作流程2.2 前后端分離開發1.導學 2.Web開發介紹 2.1 Web網站的工作流程 瀏覽器根據請求的域名請求對應的前端服務器&#xff0c;前端服務器接收到請求之后&#xff0c;把對應的前端代碼返回給服務器。瀏覽器中有解析前端代碼的解析引…

鏈路預測算法MATLAB實現

鏈路預測算法MATLAB實現 鏈路預測是復雜網絡分析中的重要任務&#xff0c;旨在預測網絡中尚未連接的兩個節點之間未來產生連接的可能性。 程序概述 MATLAB程序實現了以下鏈路預測算法&#xff1a; 基于局部信息的相似性指標&#xff08;Common Neighbors, Jaccard, Adamic-Adar…

淘寶商品詳情 API 的安全強化與生態協同創新路徑

一、安全強化&#xff1a;從 “被動防御” 到 “主動免疫” 的體系升級動態身份認證與權限顆粒化構建 “生物特征 設備指紋 行為基線” 的三重認證機制&#xff1a;結合用戶操作習慣&#xff08;如點擊間隔、滑動軌跡&#xff09;生成動態令牌&#xff0c;對高權限接口&#…