編輯:關於Android編程
最近幾天寫了一個簡單的朋友圈程序,包含了朋友圈的列表實現,視頻的錄制、預覽與上傳,圖片可選擇拍照或者從相冊選取,從相冊選取可以一次選擇多張照片,並且限制照片的張數。大致也就這些功能了。
public class FriendsListActivity extends BaseActivity implements OnRefreshListener, PostListener { private InteractionAdapter mAdapter; private PullToRefreshListView mRefreshListView; private View mFooter; private Context mContext; private Button btnToPost; protected int mPage = 0; private boolean isRefreshing = false; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.friends_list); mContext=getApplicationContext(); mAdapter = new InteractionAdapter(mContext); mAdapter.setListener(this); btnToPost=(Button) findViewById(R.id.btn_topost); mRefreshListView = (PullToRefreshListView) findViewById(R.id.friends_list); FriendsApi.getFriendsList(mContext, mCallBack); mRefreshListView.setOnRefreshListener(this); mFooter = LayoutInflater.from(mContext).inflate(R.layout.loading_footer, null); // mRefreshListView.getRefreshableView().addFooterView(mFooter); mRefreshListView.setAdapter(mAdapter); // mRefreshListView.setOnLastItemVisibleListener(mLastListener); // mRefreshListView.getRefreshableView().setDividerHeight(40); btnToPost.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { myPosts(); } }); } protected void myPosts() { new AlertDialog.Builder(this).setItems(new String[]{"圖片","視頻","文字"}, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent=new Intent(); switch (which) { case 0: intent.setClass(FriendsListActivity.this, CreatePostActivity.class); break; case 1: intent.setClass(FriendsListActivity.this, RecorderActivity.class); break; case 2: intent.setClass(FriendsListActivity.this, RecorderActivity.class); break; default: break; } startActivity(intent); } }).show(); } /** * 查看更多操作 */ @Override public void show(Interaction interaction) { } @Override public void delete(Interaction interaction) { // TODO Auto-generated method stub } @Override public void onRefresh(PullToRefreshBase refreshView) { if (!isRefreshing) { isRefreshing = true; mPage = 0; FriendsApi.getFriendsList(mContext, mCallBack); } } protected NetCallBack mCallBack = new NetCallBack() { public void friendslist(ArrayList friends) { Log.i("friends size>>>>",friends.size()+"-------------"); mAdapter.setInteractions(friends); // mRefreshListView.getLoadingLayoutProxy().setLastUpdatedLabel(null); mRefreshListView.onRefreshComplete(); isRefreshing = false; dismissLoading(); }; public void start() { showLoading(); }; public void failed(String message) { loadFailed(); }; }; @Override public void play(Interaction interaction) { Intent mIntent=new Intent(); mIntent.setClass(FriendsListActivity.this, RecorderPlayActivity.class); Bundle data = new Bundle(); data.putString("path", interaction.videoPath); mIntent.putExtras(data); startActivity(mIntent); } }
<relativelayout android:background="@color/backgroud_color" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <include android:id="@+id/list_title" android:layout_alignparenttop="true" layout="@layout/list_title"> <com.yzl.xyb.friends.refresh.view.pulltorefreshlistview android:divider="@android:color/transparent" android:id="@+id/friends_list" android:layout_below="@+id/list_title" android:layout_height="match_parent" android:layout_margin="@dimen/padding_left" android:layout_width="match_parent" app:ptrheadertextappearance="@android:style/TextAppearance.Small" app:ptrheadertextcolor="#ff666666" app:ptroverscroll="false" app:ptrshowindicator="false" xmlns:app="http://schemas.android.com/apk/res-auto"> <include layout="@layout/loading"> </include></com.yzl.xyb.friends.refresh.view.pulltorefreshlistview></include></relativelayout>
public class InteractionAdapter extends BaseAdapter implements OnClickListener { private ArrayListinteractions; private Context mContext; private FinalBitmap mFinal; private BitmapDisplayConfig config; private BitmapDisplayConfig imageConfig; private PostListener listener; public InteractionAdapter(Context context) { mContext = context; mFinal = FinalBitmap.create(mContext); Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.user_avatar); config = new BitmapDisplayConfig(); config.setAnimationType(BitmapDisplayConfig.AnimationType.fadeIn); config.setLoadfailBitmap(bitmap); config.setLoadfailBitmap(bitmap); bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.image_failed); imageConfig = new BitmapDisplayConfig(); imageConfig.setAnimationType(BitmapDisplayConfig.AnimationType.fadeIn); imageConfig.setLoadfailBitmap(bitmap); imageConfig.setLoadfailBitmap(bitmap); } public void setListener(PostListener listener) { this.listener = listener; } public void setInteractions(ArrayList interactions) { this.interactions = interactions; notifyDataSetChanged(); } @Override public int getCount() { // TODO Auto-generated method stub return interactions == null ? 0 : interactions.size(); } @Override public Object getItem(int position) { // TODO Auto-generated method stub return interactions.get(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub ViewHolder holder = null; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.friend_list_item, null); holder = new ViewHolder(); holder.avatar = (CircleImageView) convertView.findViewById(R.id.avatar); holder.content = (TextView) convertView.findViewById(R.id.content); holder.title = (TextView) convertView.findViewById(R.id.title); holder.subtitle = (TextView) convertView.findViewById(R.id.subtitle); holder.image = convertView.findViewById(R.id.image_layout); holder.image0 = (ImageView) convertView.findViewById(R.id.image0); holder.image1 = (ImageView) convertView.findViewById(R.id.image1); holder.image2 = (ImageView) convertView.findViewById(R.id.image2); holder.conments = (TextView) convertView.findViewById(R.id.conment_count); holder.praises = (TextView) convertView.findViewById(R.id.parise_count); holder.praised = (ImageView) convertView.findViewById(R.id.praise_icon); holder.more = (TextView) convertView.findViewById(R.id.more); holder.viewLayout=(LinearLayout) convertView.findViewById(R.id.view_layout); holder.surfaceView=(SurfaceView) convertView.findViewById(R.id.surface_view_result); holder.playButton=(ImageButton) convertView.findViewById(R.id.btn_play_result); holder.audioLayout=(FrameLayout) convertView.findViewById(R.id.audio_layout); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } Interaction interaction = interactions.get(position); if (TextUtils.isEmpty(interaction.avatar)) { holder.avatar.setImageBitmap(config.getLoadfailBitmap()); } else { mFinal.display(holder.avatar, interaction.avatar, config); } holder.title.setText(interaction.name); holder.subtitle.setText(interaction.subtitle); holder.content.setText(interaction.content); holder.conments.setText(String.valueOf(interaction.commentCount)); holder.praises.setText(String.valueOf(interaction.praiseCount)); int images = interaction.images == null ? 0 : interaction.images.size(); if (images > 0) { holder.image.setVisibility(View.VISIBLE); holder.audioLayout.setVisibility(View.GONE); holder.image.setOnClickListener(this); holder.image.setTag(interaction); if (images <= 1) { mFinal.display(holder.image0, interaction.images.get(0), imageConfig); holder.image1.setImageBitmap(null); holder.image2.setImageBitmap(null); } else if (images <= 2) { mFinal.display(holder.image0, interaction.images.get(0), imageConfig); mFinal.display(holder.image1, interaction.images.get(1), imageConfig); holder.image2.setImageBitmap(null); } else { mFinal.display(holder.image0, interaction.images.get(0), imageConfig); mFinal.display(holder.image1, interaction.images.get(1), imageConfig); mFinal.display(holder.image2, interaction.images.get(2), imageConfig); } } else if(interaction.videoPath!=null) { holder.image.setVisibility(View.GONE); holder.playButton.setBackgroundResource(R.drawable.play1pressed); holder.audioLayout.setVisibility(View.VISIBLE); holder.playButton.setTag(interaction); holder.playButton.setOnClickListener(this); holder.surfaceView.setTag(interaction); holder.surfaceView.setOnClickListener(this); }else{ holder.viewLayout.setVisibility(View.GONE); } holder.more.setTag(interaction); holder.more.setOnClickListener(this); return convertView; } private class ViewHolder { CircleImageView avatar; TextView title; TextView subtitle; TextView content; View image; ImageView image0; ImageView image1; ImageView image2; TextView conments; TextView praises; ImageView praised; View delete; TextView more; SurfaceView surfaceView; ImageButton playButton; FrameLayout audioLayout; LinearLayout viewLayout; } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.btn_play_result) { Interaction interaction = (Interaction) v.getTag(); }else if (id == R.id.surface_view_result) { if (this.listener != null) { this.listener.play((Interaction) v.getTag()); } }else if (id == R.id.more) { if (this.listener != null) { this.listener.show((Interaction) v.getTag()); } } else if (id == R.id.image_layout) { Intent intent = new Intent(mContext, MainActivity.class); Bundle data = new Bundle(); Interaction interaction = (Interaction) v.getTag(); data.putStringArrayList("images", interaction.images); intent.putExtras(data); mContext.startActivity(intent); } } public interface PostListener { void show(Interaction interaction); void delete(Interaction interaction); void play(Interaction interaction); }
多圖片選擇實現代碼
MultipleActivity
package com.yzl.xyb.friends; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import android.app.Activity; import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.provider.MediaStore; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.widget.GridView; import android.widget.PopupWindow.OnDismissListener; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.yzl.xyb.friends.adapter.MyAdapter; import com.yzl.xyb.friends.adapter.MyAdapter.SetCountListener; import com.yzl.xyb.friends.picture.ListImageDirPopupWindow; import com.yzl.xyb.friends.picture.ListImageDirPopupWindow.OnImageDirSelected; import com.yzl.xyb.friends.util.ImageFloder; /** * 從相冊選取圖片 * 可以選擇多張,最多可選9張 * 獲取所有相冊 * 確定:返回已選圖片的路徑 * @author hou * */ public class MultipleActivity extends Activity implements OnImageDirSelected, SetCountListener{ private TextView selectCount; private TextView selectPicture; private TextView mChooseDir; private ProgressDialog mProgressDialog; public static final int KITKAT_LESS = 2; /** * 存儲文件夾中的圖片數量 */ private int mPicsSize; /** * 圖片數量最多的文件夾 */ private File mImgDir; /** * 所有的圖片 */ private ListmImgs; private ArrayList pictures; private GridView mGirdView; private MyAdapter mAdapter; /** * 臨時的輔助類,用於防止同一個文件夾的多次掃描 */ private HashSet mDirPaths = new HashSet (); /** * 掃描拿到所有的圖片文件夾 */ private List mImageFloders = new ArrayList (); private RelativeLayout mBottomLy; int totalCount = 0; private int mScreenHeight; private ListImageDirPopupWindow mListImageDirPopupWindow; private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { mProgressDialog.dismiss(); // 為View綁定數據 data2View(); // 初始化展示文件夾的popupWindw initListDirPopupWindw(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.picture_selector); getIntent().getExtras(); DisplayMetrics outMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(outMetrics); mScreenHeight = outMetrics.heightPixels; initView(); getImages(); initEvent(); } /** * 初始化View */ private void initView() { mGirdView = (GridView) findViewById(R.id.id_gridView); mChooseDir = (TextView) findViewById(R.id.id_choose_dir); selectCount = (TextView) findViewById(R.id.tv_select_count); // allPhotoAlum = (TextView) findViewById(R.id.tv_photoAlum); selectPicture= (TextView) findViewById(R.id.tv_sure); mBottomLy = (RelativeLayout) findViewById(R.id.id_bottom_ly); } private void initEvent() { /** * 為底部的布局設置點擊事件,彈出popupWindow */ mBottomLy.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mListImageDirPopupWindow .setAnimationStyle(R.style.anim_popup_dir); mListImageDirPopupWindow.showAsDropDown(mBottomLy, 0, 0); // 設置背景顏色變暗 WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.alpha = .3f; getWindow().setAttributes(lp); } }); selectPicture.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { pictures=mAdapter.getSelectPath(); Log.i("選中的圖片1>>>>>>",pictures.size()+"----------"); Intent intent=new Intent(); // intent.setClass(MultipleActivity.this, CreatePostActivity.class); Bundle bundle=new Bundle(); bundle.putStringArrayList("PICTURES", pictures); intent.putExtras(bundle); // startActivityForResult(intent, KITKAT_LESS); setResult(KITKAT_LESS, intent); finish(); } }); } /** * 為View綁定數據 */ private void data2View() { if (mImgDir == null) { Toast.makeText(getApplicationContext(), "擦,一張圖片沒掃描到", Toast.LENGTH_SHORT).show(); return; } mImgs = Arrays.asList(mImgDir.list()); /** * 可以看到文件夾的路徑和圖片的路徑分開保存,極大的減少了內存的消耗; */ mAdapter = new MyAdapter(getApplicationContext(), mImgs, R.layout.grid_item, mImgDir.getAbsolutePath()); mAdapter.setCountListener(this); mGirdView.setAdapter(mAdapter); // allPictureCount.setText("共"+totalCount + "張"); }; /** * 初始化展示文件夾的popupWindw */ private void initListDirPopupWindw() { mListImageDirPopupWindow = new ListImageDirPopupWindow( LayoutParams.MATCH_PARENT, (int) (mScreenHeight * 1), mImageFloders, LayoutInflater.from(getApplicationContext()) .inflate(R.layout.list_dir, null)); mListImageDirPopupWindow.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss() { // 設置背景顏色變暗 WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.alpha = 1.0f; getWindow().setAttributes(lp); } }); // 設置選擇文件夾的回調 mListImageDirPopupWindow.setOnImageDirSelected(this); } /** * 利用ContentProvider掃描手機中的圖片,此方法在運行在子線程中 完成圖片的掃描,最終獲得jpg最多的那個文件夾 */ private void getImages() { if (!Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { Toast.makeText(this, "暫無外部存儲", Toast.LENGTH_SHORT).show(); return; } // 顯示進度條 mProgressDialog = ProgressDialog.show(this, null, "正在加載..."); new Thread(new Runnable() { @Override public void run() { String firstImage = null; Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; ContentResolver mContentResolver = MultipleActivity.this .getContentResolver(); // 只查詢jpeg和png的圖片 Cursor mCursor = mContentResolver.query(mImageUri, null, MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?", new String[] { "image/jpeg", "image/png" }, MediaStore.Images.Media.DATE_MODIFIED); Log.e("TAG", mCursor.getCount() + ""); while (mCursor.moveToNext()) { // 獲取圖片的路徑 String path = mCursor.getString(mCursor .getColumnIndex(MediaStore.Images.Media.DATA)); Log.e("TAG", path); // 拿到第一張圖片的路徑 if (firstImage == null) firstImage = path; // 獲取該圖片的父路徑名 File parentFile = new File(path).getParentFile(); if (parentFile == null) continue; String dirPath = parentFile.getAbsolutePath(); ImageFloder imageFloder = null; // 利用一個HashSet防止多次掃描同一個文件夾(不加這個判斷,圖片多起來還是相當恐怖的~~) if (mDirPaths.contains(dirPath)) { continue; } else { mDirPaths.add(dirPath); // 初始化imageFloder imageFloder = new ImageFloder(); imageFloder.setDir(dirPath); imageFloder.setFirstImagePath(path); } int picSize = parentFile.list(new FilenameFilter() { @Override public boolean accept(File dir, String filename) { if (filename.endsWith(".jpg") || filename.endsWith(".png") || filename.endsWith(".jpeg")) return true; return false; } }).length; totalCount += picSize; imageFloder.setCount(picSize); mImageFloders.add(imageFloder); if (picSize > mPicsSize) { mPicsSize = picSize; mImgDir = parentFile; } } mCursor.close(); // 掃描完成,輔助的HashSet也就可以釋放內存了 mDirPaths = null; // 通知Handler掃描圖片完成 mHandler.sendEmptyMessage(0x110); } }).start(); } @Override public void selected(ImageFloder floder) { mImgDir = new File(floder.getDir()); mImgs = Arrays.asList(mImgDir.list(new FilenameFilter() { @Override public boolean accept(File dir, String filename) { if (filename.endsWith(".jpg") || filename.endsWith(".png") || filename.endsWith(".jpeg")) return true; return false; } })); /** * 可以看到文件夾的路徑和圖片的路徑分開保存,極大的減少了內存的消耗; */ mAdapter = new MyAdapter(getApplicationContext(), mImgs, R.layout.grid_item, mImgDir.getAbsolutePath()); mAdapter.setCountListener(this); mGirdView.setAdapter(mAdapter); // mAdapter.notifyDataSetChanged(); // mImageCount.setText(floder.getCount() + "張"); mChooseDir.setText(floder.getName()); selectCount.setText("/9"); mListImageDirPopupWindow.dismiss(); } @Override public void doCount(int a) { selectCount.setText(a+"/9"); } }
視頻的錄制與預覽
package com.yzl.xyb.friends; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.Button; import android.widget.Toast; import com.yzl.xyb.friends.view.MovieRecorderView; import com.yzl.xyb.friends.view.MovieRecorderView.OnRecordFinishListener; /** * 錄制視頻 * @author hou * */ public class RecorderActivity extends Activity { private MovieRecorderView mRecorderView; private Button mShootBtn; private boolean isFinish = true; private String userId = ""; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.record_activity); // userId=getIntent().getParcelableExtra("userId"); mRecorderView = (MovieRecorderView) findViewById(R.id.movieRecorderView); mShootBtn = (Button) findViewById(R.id.shoot_button); mShootBtn.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { mRecorderView.record(new OnRecordFinishListener() { @Override public void onRecordFinish() { Log.i("MotionEvent>>>","ACTION_DOWN"); handler.sendEmptyMessage(1); } }); } else if (event.getAction() == MotionEvent.ACTION_UP) { Log.i("MotionEvent>>>","ACTION_UP"); if (mRecorderView.getTimeCount() > 1) handler.sendEmptyMessage(1); else { if (mRecorderView.getmVecordFile() != null) mRecorderView.getmVecordFile().delete(); mRecorderView.stop(); Toast.makeText(RecorderActivity.this, "時間太短,錄制失敗", Toast.LENGTH_SHORT).show(); } } return true; } }); } @Override public void onResume() { super.onResume(); isFinish = true; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); isFinish = false; mRecorderView.stop(); } @Override public void onPause() { super.onPause(); } @Override public void onDestroy() { super.onDestroy(); } @SuppressLint("HandlerLeak") private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { finishActivity(); Log.i("isFinish>>>",isFinish+""); } }; private void finishActivity() { if (isFinish) { mRecorderView.stop(); Intent intent = new Intent(RecorderActivity.this, TopicActivity.class); Bundle mBundle = new Bundle(); mBundle.putString("path", mRecorderView.getmVecordFile().toString()); mBundle.putString("userId", userId); intent.putExtras(mBundle); startActivity(intent); } } public interface OnShootCompletionListener { public void OnShootSuccess(String path, int second); public void OnShootFailure(); } }
package com.yzl.xyb.friends; import android.app.Activity; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.ImageView; public class RecorderPlayActivity extends Activity implements SurfaceHolder.Callback, OnClickListener { private ImageView ivBack; private ImageButton btnPlay; private SurfaceView surfaceView; private SurfaceHolder surfaceHolder; private String path=null; private MediaPlayer player; private boolean play=false; @SuppressWarnings("deprecation") @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.recorder_play); ivBack=(ImageView) findViewById(R.id.iv_back); btnPlay=(ImageButton) findViewById(R.id.ib_play); surfaceView=(SurfaceView) findViewById(R.id.play_view); btnPlay.setBackground(getResources().getDrawable(R.drawable.play1pressed)); path=this.getIntent().getStringExtra("path"); System.out.println("surface created>>>> path= "+path); surfaceHolder=surfaceView.getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setFixedSize(320, 220); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); System.out.println("oncreate--------------"); ivBack.setOnClickListener(this); btnPlay.setOnClickListener(this); surfaceView.setOnClickListener(this); } @Override public void surfaceCreated(SurfaceHolder holder) { player=new MediaPlayer(); player.setAudioStreamType(AudioManager.STREAM_MUSIC); player.setDisplay(surfaceHolder); try { System.out.println("surface created>>>> path= "+path); player.setDataSource(path); player.prepare(); } catch (Exception e) { e.printStackTrace(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub } @Override public void onClick(View v) { switch (v.getId()) { case R.id.iv_back: this.finish(); break; case R.id.ib_play: player.start(); btnPlay.setVisibility(View.GONE); break; case R.id.play_view: player.pause(); /*if(play){ player.start(); }else { player.pause(); }*/ btnPlay.setVisibility(View.VISIBLE); break; default: break; } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); if(player.isPlaying()) { player.stop(); } player.release(); } }
需求我們知道,Android系統本身有自帶的日歷控件,網絡上也有很多開源的日歷控件資源,但是這些日歷控件往往樣式較單一,API較多,不易於在實際項目中擴展並實現出符合具體
在上文當中,我們描述了如何使用TextSwitcher控件。本文將通過分析Android Framework層源碼來闡釋它是如何實現文本的平滑切換的的。TextSwitc
這兩年一直在做無線的測試,後續還會繼續去做無線的測試,但是之前因為時間的原因一直都沒有非常仔細的了解到代碼層面。近期抽空自己做了些app的開發,決定如果想把移動的測試做好
抓包(Packet Capture),實際上就是對網絡請求(包括發送與接收)的數據包進行截獲、重發、編輯、轉存等操作,在Android下,也經常被用來進行數據截取等。學會