第二章 控制項架構與自定義控制項詳解 + ListView使用技巧 + Scroll分析

来源:http://www.cnblogs.com/linghu-java/archive/2016/06/21/5604999.html
-Advertisement-
Play Games

1.Android控制項架構下圖是UI界面架構圖,每個Activity都有一個Window對象,通常是由PhoneWindow類來實現的。PhoneWindow將DecorView作為整個應用視窗的根View,DecorView將屏幕分成兩部分:TitleView和ContentView。Conten ...


1.Android控制項架構
下圖是UI界面架構圖,每個Activity都有一個Window對象,通常是由PhoneWindow類來實現的。
PhoneWindow將DecorView作為整個應用視窗的根View,DecorView將屏幕分成兩部分:TitleView和ContentView。
ContentView實際上是一個FrameLayout,裡面容納的就是我們在xml佈局文件中定義的佈局。
img

為什麼調用requestWindowFeature()方法一定要在setContentView()方法調用之前?
當程式在onCreate()方法中調用setContentView()方法後,ActivityManagerService會回調onResume()方法,此時系統才會將整個DecorView添加到PhoneWindow中,並讓其顯示出來,從而完成界面的繪製。

2.View的測量:MeasureSpec和測量模式
MeasureSpec是一個32位的int值,其中高2位是測量的模式,低30位是測量的大小 (使用位運算是為了提高效率和節省空間)
測量模式有三種:
(1)EXACTLY:精確值模式,屬性設置為精確數值或者match_parent時,系統使用的是EXACTLY模式
(2)AT_MOST:最大值模式,屬性設置為wrap_content時,系統使用的是AT_MOST模式
(3)UNSPECIFIED:不指定大小測量模式,通常情況下在繪製自定義View時才會用到

View類預設的onMeasure()方法只支持EXACTLY模式,所以如果在自定義View的時候不重寫onMeasure方法的話,就只能使用EXACTLY模式。自定義View可以響應你指定的具體的寬高值或者是match_parent屬性,但是,如果要讓自定義View支持wrap_content屬性的話,那麼就必須要重寫onMeasure方法來指定wrap_content時view的大小。

重寫onMeasure方法的最終工作就是把測量後的寬高值作為參數設置給setMeasuredDimension方法。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//計算width和height
setMeasuredDimension(width, height);
}

 

3.View和ViewGroup的繪製
View的onDraw()方法包含一個參數Canvas對象,使用這個Canvas對象就可以進行繪圖了。

通常情況下,Canvas對象的創建需要傳入參數Bitmap,為什麼呢?
這是因為傳進去的Bitmap與通過這個Bitmap創建的Canvas畫布是緊緊聯繫在一起的,這個Bitmap用來存儲所有繪製在Canvas上的像素信息,當使用Bitmap創建Canvas之後,後面調用所有的Canvas.drawXXX方法都發生在這個Bitmap上。

ViewGroup通常不需要繪製,因為它本身沒有需要繪製的東西,如果不指定ViewGroup的背景顏色,那麼ViewGroup的onDraw方法都不會被調用。但是,ViewGroup會調用dispatchDraw方法來繪製其子view,其過程同樣是通過遍歷所有子view並調用子view的繪製方法來完成繪製工作的。

4.自定義View(ViewGroup)
三種自定義View的方式:
(1)對現有控制項進行擴展
對現有控制項進行擴展的代碼結構通常如下:

@Override
protected void onDraw(Canvas canvas) {
//在回調父類方法之前實現自己的邏輯,對TextView來說就是在繪製文本之前
super.onDraw(canvas);
//在回調父類方法之後實現自己的邏輯,對TextView來說就是在繪製文本之後
}

 

例如,書中對TextView進行擴展代碼節選

private void initView() {
mPaint1 = new Paint();
mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
mPaint1.setStyle(Paint.Style.FILL);
mPaint2 = new Paint();
mPaint2.setColor(Color.YELLOW);
mPaint2.setStyle(Paint.Style.FILL);
}

@Override
protected void onDraw(Canvas canvas) {
// 繪製外層矩形
canvas.drawRect(
0,
0,
getMeasuredWidth(),
getMeasuredHeight(),
mPaint1);
// 繪製內層矩形
canvas.drawRect(
10,
10,
getMeasuredWidth() - 10,
getMeasuredHeight() - 10,
mPaint2);
canvas.save();
// 繪製文字前平移10像素
canvas.translate(10, 0);
// 父類完成的方法,即繪製文本
super.onDraw(canvas);
canvas.restore();
}

 

(2)通過組合來實現新的控制項
這種方式通常需要繼承一個合適的ViewGroup,再給它添加指定功能的控制項,從而組合成新的複合控制項。
[項目中一般使用這種方式創建應用內統一的提示信息界面,可以是提示正在載入,也可以是提示數據出錯了等]
例如,書中的TopBar例子:

public class TopBar extends RelativeLayout {

// 包含topbar上的元素:左按鈕、右按鈕、標題
private Button mLeftButton, mRightButton;
private TextView mTitleView;

// 佈局屬性,用來控制組件元素在ViewGroup中的位置
private LayoutParams mLeftParams, mTitlepParams, mRightParams;

// 左按鈕的屬性值,即我們在atts.xml文件中定義的屬性
private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
// 右按鈕的屬性值,即我們在atts.xml文件中定義的屬性
private int mRightTextColor;
private Drawable mRightBackground;
private String mRightText;
// 標題的屬性值,即我們在atts.xml文件中定義的屬性
private float mTitleTextSize;
private int mTitleTextColor;
private String mTitle;

// 映射傳入的介面對象
private topbarClickListener mListener;
......
}

 

(3)重寫View來實現全新的控制項
創建自定義View的難點在於繪製控制項和實現交互,通常需要繼承View類,並重寫onDraw、onMeasure等方法來實現繪製邏輯,同時通過重寫onTouchEvent等觸控事件方法來實現交互邏輯。
[這類自定義View是比較常用的,自己以前也寫過幾個簡單的例子,參見AnnotationViewProgressView項目,或者參考之前的博文Android Text View with Custom Font,一個可以自定義字體的TextView]
例如,書中的弧線展示圖例子

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec);
mMeasureHeigth = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mMeasureWidth, mMeasureHeigth);
initView();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 繪製圓
canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);
// 繪製弧線
canvas.drawArc(mArcRectF, 270, mSweepAngle, false, mArcPaint);
// 繪製文字
canvas.drawText(mShowText, 0, mShowText.length(), mCircleXY, mCircleXY + (mShowTextSize / 4), mTextPaint);
}

 

5.事件攔截機制分析 [後面有專門對Android事件攔截機制分析的部分,此處略過]

 

第四章 ListView使用技巧

1.使用ViewHolder模式提高效率
這種方式是必須要用的!下麵的例子是一個常見的使用ViewHolder並且包含多個item type的Adapter例子:

public class ChatItemListViewAdapter extends BaseAdapter {

private List<ChatItemListViewBean> mData;
private LayoutInflater mInflater;

public ChatItemListViewAdapter(Context context, List<ChatItemListViewBean> data) {
this.mData = data;
mInflater = LayoutInflater.from(context);
}

@Override
public int getCount() {
return mData.size();
}

@Override
public Object getItem(int position) {
return mData.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public int getItemViewType(int position) {
ChatItemListViewBean bean = mData.get(position);
return bean.getType();
}

@Override
public int getViewTypeCount() {
return 2;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
if (getItemViewType(position) == 0) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.chat_item_itemin, null);
holder.icon = (ImageView) convertView.findViewById(R.id.icon_in);
holder.text = (TextView) convertView.findViewById(R.id.text_in);
} else {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.chat_item_itemout, null);
holder.icon = (ImageView) convertView.findViewById(R.id.icon_out);
holder.text = (TextView) convertView.findViewById(R.id.text_out);
}
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.icon.setImageBitmap(mData.get(position).getIcon());
holder.text.setText(mData.get(position).getText());
return convertView;
}

public final class ViewHolder {
public ImageView icon;
public TextView text;
}

}

 

2.listview的一些屬性設置
(1)設置分隔線
android:divider=""@android:color/white"
android:dividerHeight="10dp"
android:divider="@null" (設置分隔線透明)
(2)隱藏滾動條
android:scrollbars="none"
(3)取消item的點擊效果
android:listSelector="@android:color/transparent"

3.listview的一些方法設置
(1)設置listview顯示在第幾項
listview.setSelection(n); 這個方法類似scrollTo瞬間完成移動,平滑移動可以使用下麵的方式
listview.smoothScrollBy(distance, duration);
listview.smoothScrollByOffset(offset);
listview.smoothScrollToPosition(index);
(2)處理空listview
listview.setEmptyView(View)

4.動態修改listview
在使用adapter的notifyDataSetChanged方法時,必須保證傳進adapter的數據list和發生數據變化的list是同一個對象,否則將無法看到效果。

5.listview滑動監聽
監聽listview的滑動事件的方法有兩種:一個是OnTouchListener來實現監聽,另一個是使用OnScrollListener來實現監聽。
例如,書中實現了一個監聽listview上下滑動事件操縱toolbar顯示和隱藏效果的例子:

public class ScrollHideListView extends Activity {

private Toolbar mToolbar;
private ListView mListView;
private String[] mStr = new String[20];
private int mTouchSlop;
private float mFirstY;
private float mCurrentY;
private int direction;
private ObjectAnimator mAnimator;
private boolean mShow = true;

View.OnTouchListener myTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mFirstY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
mCurrentY = event.getY();
if (mCurrentY - mFirstY > mTouchSlop) {
direction = 0;// down
} else if (mFirstY - mCurrentY > mTouchSlop) {
direction = 1;// up
}
if (direction == 1) {
if (mShow) {
toolbarAnim(1);//show
mShow = !mShow;
}
} else if (direction == 0) {
if (!mShow) {
toolbarAnim(0);//hide
mShow = !mShow;
}
}
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scroll_hide);
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mListView = (ListView) findViewById(R.id.listview);
for (int i = 0; i < mStr.length; i++) {
mStr[i] = "Item " + i;
}
View header = new View(this);
header.setLayoutParams(new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
(int) getResources().getDimension( R.dimen.abc_action_bar_default_height_material)));
mListView.addHeaderView(header);
mListView.setAdapter(new ArrayAdapter<String>(
ScrollHideListView.this,
android.R.layout.simple_expandable_list_item_1,
mStr));
mListView.setOnTouchListener(myTouchListener);
}

private void toolbarAnim(int flag) {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
if (flag == 0) {
mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY", mToolbar.getTranslationY(), 0);
} else {
mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY", mToolbar.getTranslationY(), -mToolbar.getHeight());
}
mAnimator.start();
}
}

 

監聽listview的OnScrollListener的一般實現

mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState){
case SCROLL_STATE_IDLE://滑動停止時
break;
case SCROLL_STATE_TOUCH_SCROLL://正在滑動時
break;
case SCROLL_STATE_FLING://手指拋動之後listview由於慣性繼續滑動
break;
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//firstVisibleItem 第一個可見的item的id
//visibleItemCount 可見的item的總數
//totalItemCount 所有item的總數
}
});

 

獲得當前可視的item的位置等信息的便捷方法

mListView.getLastVisiblePosition();//獲取可視區域最後一個item的id
mListView.getFirstVisiblePosition();//獲取可視區域第一個item的id

 

 

第五章 Android Scroll分析

1.獲取坐標值的各種方法
圖片來自Android中的坐標系以及獲取坐標的方法
img

2.實現滑動的基本思想
當觸摸view時,系統記下當前觸摸點坐標;當手指移動時,系統記下移動後的觸摸點坐標,從而獲取到相對於前一次坐標點的偏移量,並通過偏移量來修改view的坐標,這樣不斷重覆,從而實現滑動過程。

3.常用的滑動實現方法
(1)修改view的left、top、right和bottom的值:調用layout方法或者offsetLeftAndRight等方法
絕對坐標系下

// 絕對坐標方式
@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) (event.getRawX());
int rawY = (int) (event.getRawY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 記錄觸摸點坐標
lastX = rawX;
lastY = rawY;
break;
case MotionEvent.ACTION_MOVE:
// 計算偏移量
int offsetX = rawX - lastX;
int offsetY = rawY - lastY;
// 在當前left、top、right、bottom的基礎上加上偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
// 重新設置初始坐標
lastX = rawX;
lastY = rawY;
break;
}
return true;
}

 

視圖坐標系下

// 視圖坐標方式
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 記錄觸摸點坐標
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 計算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 在當前left、top、right、bottom的基礎上加上偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
//offsetLeftAndRight(offsetX);
//offsetTopAndBottom(offsetY);
break;
}
return true;
}

 

(2)修改佈局參數LayoutParams:修改子view的getLayoutParams或者使用ViewGroup.MarginLayoutParams
子view的getLayoutParams得到的LayoutParams需要和父ViewGroup的Layout類型一致,如果使用ViewGroup.MarginLayoutParams的話那就方便一些,不需要考慮父ViewGroup的具體類型。

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 記錄觸摸點坐標
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
// 計算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
//ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
}
return true;
}

 

(3)使用scrollTo和scrollBy方法
scrollTo和scrollBy方法移動的是view的content,即讓view的內容移動。如果在ViewGroup中使用scrollTo或者scrollBy方法,那麼移動的是所有子view。但如果在view中使用,那麼移動的將是view的內容,例如TextView,content就是它的文本;ImageView,content就是它的drawable對象。

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);//註意這裡需要使用負號進行移動!
break;
}
return true;
}

 

(4)使用Scroller實現平滑效果
前面的滑動都不是平滑的,而Scroller是可以實現平滑效果的,它的實現原理很簡單,其實就是不斷調用scrollTo和scrollBy方法來實現view的平滑移動,因為人眼的視覺暫留特性看起來就是平滑的。
使用Scroller主要有三個步驟:
1.初始化Scroller對象,一般在view初始化的時候同時初始化scroller;
2.重寫view的computeScroll方法,computeScroll方法是不會自動調用的,只能通過invalidate->draw->computeScroll來間接調用,實現迴圈獲取scrollX和scrollY的目的,當移動過程結束之後,Scroller.computeScrollOffset方法會返回false,從而中斷迴圈;
3.調用Scroller.startScroll方法,將起始位置、偏移量以及移動時間(可選)作為參數傳遞給startScroll方法。

例如,書中給出的例子,子view在被拖動之後會自動平滑移動到原來的位置

private void ininView(Context context) {
setBackgroundColor(Color.BLUE);
// 初始化Scroller
mScroller = new Scroller(context);
}

@Override
public void computeScroll() {
super.computeScroll();
// 判斷Scroller是否執行完畢
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo( mScroller.getCurrX(), mScroller.getCurrY());
// 通過重繪來不斷調用computeScroll
invalidate();//很重要
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
case MotionEvent.ACTION_UP:
// 手指離開時,執行滑動過程
View viewGroup = ((View) getParent());
mScroller.startScroll( viewGroup.getScrollX(), viewGroup.getScrollY(),
-viewGroup.getScrollX(), -viewGroup.getScrollY());
invalidate();//很重要
break;
}
return true;
}

 

(5)屬性動畫 [後面有專門對Android動畫分析的部分,此處略過]

(6)使用ViewDragHelper
ViewDragHelper類使用較少,它是support庫中DrawerLayout和SlidingPaneLayout內部實現的重要類!
之前讀過類似側邊欄菜單的實現代碼(SlidingMenu),個人感覺ViewDragHelper其實是更高層次的封裝,將這類效果所需的介面暴露出來以簡化類似的開發工作,書中給了一個例子,介紹了ViewDragHelper的使用,實現了類似側邊欄菜單的效果

public class DragViewGroup extends FrameLayout {

private ViewDragHelper mViewDragHelper;
private View mMenuView, mMainView;
private int mWidth;

public DragViewGroup(Context context) {
super(context);
initView();
}

public DragViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}

public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = mMenuView.getMeasuredWidth();
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
//將觸摸事件傳遞給ViewDragHelper,此操作必不可少
mViewDragHelper.processTouchEvent(event);
return true;
}

private void initView() {
mViewDragHelper = ViewDragHelper.create(this, callback);
}

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {

// 何時開始檢測觸摸事件
@Override
public boolean tryCaptureView(View child, int pointerId) {
//如果當前觸摸的child是mMainView時開始檢測
return mMainView == child;
}

// 觸摸到View後回調
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}

// 當拖拽狀態改變,比如idle,dragging
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}

// 當位置改變的時候調用,常用與滑動時更改scale等
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}

// 處理垂直滑動
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}

// 處理水平滑動
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}

// 拖動結束後調用
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
//手指抬起後緩慢移動到指定位置
if (mMainView.getLeft() < 500) {
//關閉菜單,相當於Scroller的startScroll方法
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
} else {
//打開菜單
mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};

@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 近幾年,人臉識別技術在身份認證領域的應用已經有了較多應用,例如:支付寶、招行的取款、養老金領取等方面,但在杜絕假冒、認證安全性等方面,目前還是一個比較需要進一步解決的課題,特別是在移動端的活體認證技術方面。 本文介紹了在HTML5 環境下可以採用clmtrackr.js 檢測工具,結合人臉模型,實現 ...
  • 1、日期格式轉時間戮 2、時間戮轉日期格式 ...
  • 經常我們看電影都會看到電影結束後會出現一段介紹這個電影的導演、編劇、主演、友情出演等等一些電影信息的滾動字幕,那麼那個效果可以用html+css+div實現。具體實現代碼如下: html代碼: 滾動標簽:<marquee>要滾動的文字/圖像<img src=””> </marquee> 下麵是我對滾 ...
  • 1,給第一人稱控制器添加腳本:playercollisions.js 腳本中只定義變數,先不添加方法: #pragma strict var door_open_time:float=3.0; var door_open_sound:AudioClip; var door_shut_sound:Au ...
  • 最近在做JS演算法項目時發現一個令我匪夷所思的問題, 這裡想記錄一下問題。 首先介紹一下字元串replace()方法的基本用法。 replace() 方法使用一個替換值(replacement)替換掉一個匹配模式(pattern)在原字元串中某些或所有的匹配項,並返回替換後的字元串。這個替換模式可以是 ...
  • App Store: Pinyin Comparison 拼音辨別 本人不懂韓文和日文,靠的是谷歌翻譯,希望不要被噴 1. 支持韓文和日文 2. 豐富字詞庫 1. 지원 한국과 일본 2. 풍부한 단어의 동의어 사전 1.サポート韓國と日本 2.豊富な単語シソーラス App Store: Piny ...
  • 一,效果圖。 二,工程圖。 三,代碼。 RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIViewController <UITextViewDelegate> { UITextView *p ...
  • 通常情況下,只有高端用戶才會經常用到終端應用。這並不意味著命令行非常難學,有的時候命令行可以輕鬆、快速的解決問題。相信所有Mac用戶都嘗試過命令行,今天為大家帶來9個非常實用的命令行操作。一些命令行需要安裝Xcode之後才可以實用,Xcode在Mac App Store中免費供應。1.使用caffe ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...