編輯:關於Android編程
在我們第一次安裝app的時候,有一些app會出現一個覆蓋在我們原來View上面的浮層view,用來做一個app的指示,讓用戶更快的知道app的整體架構和功能點。下面大家看一下效果圖;
這就是我們要出現的效果。首先我們需要實現的功能點大概就是 ,在第一次安裝的時候我們才會去觸發這個浮層view的效果,我們就需要去保存這個。我們還需要去繪制一個浮層view在蓋在原有的view上面,需要寫一個列表,當他下拉的時候,符合某些條件就觸發一個浮層。大概的要求我們知道了就可以開工了,
我們先看一下整體的項目結構: 大概的架構也躍然於屏了,獲取屏幕像素的工具類,浮層view管理者,構造類,對象類,主類實現 效果和一點小小邏輯。
我們先從 像素工具類開始看:
ScreenUtils package com.newbieguide; import android.app.Activity; import android.app.Application; import android.content.Context; import android.graphics.Rect; import android.util.DisplayMetrics; import java.lang.reflect.Field; /** * 獲取手機屏幕參數的功能工具類 * Created by nzx on 2016/9/2. * 屏幕區域的獲取 :就是整個手機的可見范圍 * activity.getWindowManager().getDefaultDisplay(); * 應用區域的獲取:就是沒有包括 那些 應用商,時間的那個欄 剩下的部分=屏幕部分減去時間欄 * * Rect outRect = new Rect(); * activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect); * 我們繪制view的繪制:就是應用區域 減去(我們沒有隱藏標題欄應用狀態下( this.requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉標題欄 ) )的部分=View 部分 * * Rect outRect = new Rect(); * activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(outRect); */ public class ScreenUtils { private ScreenUtils() { throw new AssertionError(); } //屏幕距離(dp)轉換成像素距離(px) public static float dpToPx(Context context, float dp) { if (context == null) { return -1; } return dp * context.getResources().getDisplayMetrics().density; } public static int dpToPx(Context context, int dp) { return (int) (dp * context.getResources().getDisplayMetrics().density); } /** * 獲取屏幕寬度 * DisplayMetrics 這是 一個系統 用來 專門 管理 手機 像素 屏幕尺寸的類 */ public static int getScreenWidth(Context context) { DisplayMetrics dm = context.getResources().getDisplayMetrics(); int screenHeight = dm.widthPixels; return screenHeight; } /** * 獲取屏幕高度 */ public static int getScreenHeight(Context context) { DisplayMetrics dm = context.getResources().getDisplayMetrics(); int screenHeight = dm.heightPixels; return screenHeight; } /** * 獲取狀態欄的高 */ public static int getStatusBarHeight(Activity context) { Rect frame = new Rect(); context.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); int statusBarHeight = frame.top; if (0 == statusBarHeight) { statusBarHeight = getStatusBarHeightByReflection(context); } return statusBarHeight; } public static int getStatusBarHeight1(Activity context) { Rect rect = new Rect(); context.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); int statuBarHeight = rect.top; if (0 == statuBarHeight) { statuBarHeight = getStatusBarHeightByReflection(context); } return statuBarHeight; } //獲取狀態欄高度 。 public static int getStatusBarHeightByReflection(Context context) { Classc; Object obj; Field field; // 默認為38,貌似大部分是這樣的 int x, statusBarHeight = 38; try { c = Class.forName("com.android.internal.R$dimen"); obj = c.newInstance(); field = c.getField("status_bar_height"); x = Integer.parseInt(field.get(obj).toString()); statusBarHeight = context.getResources().getDimensionPixelSize(x); } catch (Exception e1) { e1.printStackTrace(); } return statusBarHeight; } }我生疏一點的方法我都會去寫上注釋這樣看來 也比較輕松。
下面 我們開始繪制 我們的表面的浮層view了
GuideView
package com.newbieguide; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.util.AttributeSet; import android.widget.RelativeLayout; import java.util.List; /** * Created by nzx on 2016/9/2. * 我們實現浮層指引view的業務代碼邏輯的實現 */ public class GuideView extends RelativeLayout { private int mBgColor = 0xb2000000; private float mStrokeWidth; private Paint mPaint; private Bitmap mBitmap; private RectF mBitmapRect; private Canvas mCanvas; private List浮層view 的一些封裝的對象類mHoleList; private PorterDuffXfermode pdf; public GuideView(Context context) { super(context); init(); } public GuideView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public GuideView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { pdf = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(mBgColor); mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.INNER)); mBitmapRect = new RectF(); setClickable(true); setWillNotDraw(false); } public void setDate(List holeList) { mHoleList = holeList; if (mHoleList != null && !mHoleList.isEmpty()) { for (HoleBean hole : mHoleList) { mBitmapRect.union(hole.getRectF()); } } mStrokeWidth = Math.max(Math.max(mBitmapRect.left, mBitmapRect.top), Math.max(ScreenUtils.getScreenWidth(getContext()) - mBitmapRect.right, ScreenUtils.getScreenHeight(getContext()) - mBitmapRect.bottom)); if (mBitmapRect.width() > 0 && mBitmapRect.height() > 0) { mBitmap = Bitmap.createBitmap((int) mBitmapRect.width(), (int) mBitmapRect.height(), Bitmap.Config.ARGB_8888); } else { mBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888); throw new UnsupportedOperationException("需要高亮的view尚未加載完,請調整適當的時機或者延遲"); } mCanvas = new Canvas(mBitmap); mCanvas.drawColor(mBgColor); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mHoleList != null && mHoleList.size() > 0) { mPaint.setXfermode(pdf); mPaint.setStyle(Paint.Style.FILL); for (HoleBean hole : mHoleList) { RectF rectF = hole.getRectF(); rectF.offset(-mBitmapRect.left, -mBitmapRect.top); switch (hole.getType()) { case HoleBean.TYPE_CIRCLE: mCanvas.drawCircle(rectF.centerX(), rectF.centerY(), hole.getRadius(), mPaint); break; case HoleBean.TYPE_RECTANGLE: mCanvas.drawRect(rectF, mPaint); break; case HoleBean.TYPE_OVAL: mCanvas.drawOval(rectF, mPaint); break; } } canvas.drawBitmap(mBitmap, mBitmapRect.left, mBitmapRect.top, null); //繪制剩余空間的矩形 mPaint.setXfermode(null); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth + 0.1f); canvas.drawRect(fillRect(mBitmapRect), mPaint); } } private RectF fillRect(RectF rectF) { RectF fillRect = new RectF(); fillRect.left = rectF.left - mStrokeWidth / 2; fillRect.top = rectF.top - mStrokeWidth / 2; fillRect.right = rectF.right + mStrokeWidth / 2; fillRect.bottom = rectF.bottom + mStrokeWidth / 2; return fillRect; } public void recycler() { if (mBitmap != null) { mBitmap.recycle(); mBitmap = null; } } }
HoleBean
package com.newbieguide; import android.graphics.RectF; import android.view.View; /** * Created by nzx on 2016/9/2. *改變原有的對象類 ,覆蓋在原有層上面的表面view */ public class HoleBean { public static final int TYPE_CIRCLE = 0; //圓 public static final int TYPE_RECTANGLE = TYPE_CIRCLE + 1; //長方形 public static final int TYPE_OVAL = TYPE_RECTANGLE + 1; //橢圓 private View mHole; private int mType; public HoleBean(View hole, int type) { this.mHole = hole; this.mType = type; } //獲得我們需要繪制的浮層view 寬,高 public int getRadius() { return mHole != null ? Math.min(mHole.getWidth(), mHole.getHeight()) / 2 : 0; } public RectF getRectF() { RectF rectF = new RectF(); if (mHole != null) { int[] location = new int[2]; mHole.getLocationOnScreen(location); rectF.left = location[0]; rectF.top = location[1]; rectF.right = location[0] + mHole.getWidth(); rectF.bottom = location[1] + mHole.getHeight(); } return rectF; } public int getType() { return mType; } }下面就是我們怎麼去設置浮層view的管理者類咯,這裡對於我們來說就比較重要,想要什麼樣式,點擊事件,時間的回調,什麼字的那個操作都需要在這邊進行修改。
NewbieGuide
package com.newbieguide; import android.app.Activity; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import java.util.ArrayList; import java.util.List; /** * Created by nzx on 2016/9/2. * 新的頁面層 */ public class NewbieGuide { public static final int CENTER = 0; private boolean mEveryWhereTouchable = true; private OnGuideChangedListener mOnGuideChangedListener; private List下面這個類 我們就實現了 一個相關的點擊等時間了 比如怎麼設置 高亮區,我們的按鈕回調等,像是我們需要很多自定義操作都在這裡可以實現mHoleList; private Activity mActivity; private GuideView mGuideView; private FrameLayout mParentView; public NewbieGuide(Activity activity) { init(activity); } //初始化我們的整個視圖 private NewbieGuide init(Activity activity) { mActivity = activity; mParentView = (FrameLayout) mActivity.getWindow().getDecorView(); mGuideView = new GuideView(mActivity); mHoleList = new ArrayList<>(); return this; } //添加 我們高亮view的 視圖 對象 public NewbieGuide addHighLightView(View view, int type) { HoleBean hole = new HoleBean(view, type); mHoleList.add(hole); return this; } //蒙版提示圖標 public NewbieGuide addIndicateImg(int id, int offsetX, int offsetY) { ImageView arrowImg = new ImageView(mActivity); arrowImg.setImageResource(id); mGuideView.addView(arrowImg, getLp(offsetX, offsetY)); return this; } // 蒙版提示的消息 public NewbieGuide addMessage(String msg, int offsetX, int offsetY) { mGuideView.addView(generateMsgTv(msg), getLp(offsetX, offsetY)); return this; } //蒙版提示的消息文本 public NewbieGuide addKnowTv(String text, int offsetX, int offsetY) { mGuideView.addView(generateKnowTv(text), getLp(offsetX, offsetY)); return this; } //蒙版提示的消息文本的顯示位置 public NewbieGuide addMsgAndKnowTv(String msg, int offsetY) { mGuideView.addView(generateMsgAndKnowTv(msg), getLp(CENTER, offsetY)); return this; } //生成提示文本 private TextView generateMsgTv(String msg) { TextView msgTv = new TextView(mActivity); msgTv.setText(msg); msgTv.setTextColor(0xffffffff); msgTv.setTextSize(15); //設置文本的行距間距 msgTv.setLineSpacing(ScreenUtils.dpToPx(mActivity, 5), 1f); //文本的相對位置 msgTv.setGravity(Gravity.CENTER); return msgTv; } //生成我知道了文本 private TextView generateKnowTv(String text) { TextView knowTv = new TextView(mActivity); knowTv.setTextColor(0xffffffff); knowTv.setTextSize(15); knowTv.setPadding(ScreenUtils.dpToPx(mActivity, 15), ScreenUtils.dpToPx(mActivity, 5), ScreenUtils.dpToPx(mActivity, 15), ScreenUtils.dpToPx(mActivity, 5)); knowTv.setBackgroundResource(R.drawable.solid_white_bg); knowTv.setText(text); knowTv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mActivity,"繼續下一個動畫",Toast.LENGTH_LONG).show(); remove(); } }); return knowTv; } private TextView generateKnowTv1(String text) { TextView knowTv1 = new TextView(mActivity); knowTv1.setTextColor(0xffffffff); knowTv1.setTextSize(15); knowTv1.setPadding(ScreenUtils.dpToPx(mActivity, 15), ScreenUtils.dpToPx(mActivity, 5), ScreenUtils.dpToPx(mActivity, 15), ScreenUtils.dpToPx(mActivity, 5)); knowTv1.setBackgroundResource(R.drawable.solid_white_bg); knowTv1.setText(text); knowTv1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { remove(); } }); return knowTv1; } //生成提示文本和我知道了文本 private LinearLayout generateMsgAndKnowTv(String msg) { LinearLayout container = new LinearLayout(mActivity); container.setOrientation(LinearLayout.VERTICAL); container.setGravity(Gravity.CENTER_HORIZONTAL); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams .WRAP_CONTENT); container.addView(generateMsgTv(msg), lp); lp.topMargin = ScreenUtils.dpToPx(mActivity, 10); container.addView(generateKnowTv("就你TM話多"), lp); return container; } //生成布局參數 private RelativeLayout.LayoutParams getLp(int offsetX, int offsetY) { RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams .WRAP_CONTENT); //水平方向 if (offsetX == CENTER) { lp.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE); } else if (offsetX < 0) { lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); lp.rightMargin = -offsetX; } else { lp.leftMargin = offsetX; } //垂直方向 if (offsetY == CENTER) { lp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); } else if (offsetY < 0) { lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); lp.bottomMargin = -offsetY; } else { lp.topMargin = offsetY; } return lp; } public void show() { int paddingTop = ScreenUtils.getStatusBarHeight(mActivity); mGuideView.setPadding(0, paddingTop, 0, 0); mGuideView.setDate(mHoleList); mParentView.addView(mGuideView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams .MATCH_PARENT)); if (mOnGuideChangedListener != null) mOnGuideChangedListener.onShowed(); if (mEveryWhereTouchable) { mGuideView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { remove(); return false; } }); } } public void remove() { if (mGuideView != null && mGuideView.getParent() != null) { mGuideView.recycler(); ((ViewGroup) mGuideView.getParent()).removeView(mGuideView); if (mOnGuideChangedListener != null) { mOnGuideChangedListener.onRemoved(); } } } public NewbieGuide setEveryWhereTouchable(boolean everyWhereTouchable) { mEveryWhereTouchable = everyWhereTouchable; return this; } public void setOnGuideChangedListener(OnGuideChangedListener onGuideChangedListener) { this.mOnGuideChangedListener = onGuideChangedListener; } //浮層顯示後的回調 public interface OnGuideChangedListener { void onShowed(); void onRemoved(); } }
NewbieGuideManager
package com.newbieguide; import android.app.Activity; import android.content.SharedPreferences; import android.os.Handler; import android.view.View; import android.widget.Toast; /** * Created by nzx on 2016/9/2. * 新的指引頁面管理者 */ public class NewbieGuideManager { static final String TAG = "newbie_guide"; public static final int TYPE_LIST = 0;// list public static final int TYPE_COLLECT = 1;// 收藏 Activity mActivity; SharedPreferences sp; NewbieGuide mNewbieGuide; int mType; public NewbieGuideManager(Activity activity, int type) { mNewbieGuide = new NewbieGuide(activity); sp = activity.getSharedPreferences(TAG, Activity.MODE_PRIVATE); mActivity = activity; mType = type; } //添加 我們高亮view的 視圖 public NewbieGuideManager addView(View view, int shape) { mNewbieGuide.addHighLightView(view, shape); return this; } public void show() { show(0); } public void show(int delayTime) { SharedPreferences.Editor editor = sp.edit(); editor.putBoolean(TAG + mType, false); editor.apply(); new Handler().postDelayed(new Runnable() { @Override public void run() { switch (mType) { case TYPE_LIST: mNewbieGuide.setEveryWhereTouchable(false).addIndicateImg(R.drawable.left_arrow, ScreenUtils.dpToPx(mActivity, 60), ScreenUtils.dpToPx(mActivity, 110)).addMsgAndKnowTv("這個listview滾動到item6後出現新手引導浮層,\n只有點擊我知道啦才會想消失", -ScreenUtils.dpToPx(mActivity, 250)).show(); Toast.makeText(mActivity,"辣雞",Toast.LENGTH_LONG).show(); break; case TYPE_COLLECT: mNewbieGuide.addIndicateImg(R.drawable.right, ScreenUtils.dpToPx(mActivity, -70), ScreenUtils.dpToPx(mActivity, 50)).addMsgAndKnowTv("寫了一個測試的,隨便點擊哪裡都可以\n廢話就不那麼多了,你們看看吧", ScreenUtils.dpToPx(mActivity, 150)).show(); break; } } }, delayTime); } public void showWithListener(int delayTime, NewbieGuide.OnGuideChangedListener onGuideChangedListener) { mNewbieGuide.setOnGuideChangedListener(onGuideChangedListener); show(delayTime); } /** * 判斷新手引導也是否已經顯示了 */ public static boolean isNeverShowed(Activity activity, int type) { return activity.getSharedPreferences(TAG, Activity.MODE_PRIVATE).getBoolean(TAG + type, true); } }下面 就開始寫我們的listview列表了,這個都很熟悉了 適配器 布局item 都是輕車熟路了 布局我就不貼了 後面可以拿demo看
ListAdapter
package com.newbieguide; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.List; /** * Created by nzx on 2016/9/2. */ class ListAdapter extends BaseAdapter { private List下面的MainActivity 實現的邏輯 就是第一次一進來appmList; private LayoutInflater inflater; private Context context; public ListAdapter(List list,Context context) { mList = list; this.context = context; this.inflater = LayoutInflater.from(context); } @Override public int getCount() { return mList != null ? mList.size() : 0; } @Override public Object getItem(int position) { return mList != null ? mList.get(position) : null; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder holder; if(convertView == null) { holder = new ViewHolder(); convertView = View.inflate(context,R.layout.item, null) ; holder.logo = (ImageView) convertView.findViewById(R.id.logo); holder.item = (TextView) convertView.findViewById(R.id.details); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.item.setText(mList.get(position).toString()); return convertView; } } class ViewHolder { ImageView logo; TextView item; }
package com.newbieguide; import android.app.Activity; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import java.security.KeyStore; import java.util.ArrayList; import java.util.List; public class MainActivity extends Activity implements AbsListView.OnScrollListener { private ImageView mCollect; private TextView mTitleTv; private ListView mListView; private ListmList; private boolean isShow; SharedPreferences sp; private boolean isFirst = false;//是否第一次打開App private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); initData(); } private void initData() { sp= getSharedPreferences("WelcomeActivity", 0); isFirst = sp.getBoolean("isFirst",true); //判斷是否第一次打開App,是的話跳轉到引導頁,否則跳轉到主頁 if (isFirst) { handler.postDelayed(new Runnable() { @Override public void run() { /** if (NewbieGuideManager.isNeverShowed(MainActivity.this, NewbieGuideManager.TYPE_COLLECT)) { new NewbieGuideManager(MainActivity.this, NewbieGuideManager.TYPE_COLLECT).addView(mCollect, HoleBean.TYPE_CIRCLE).addView(mTitleTv, HoleBean.TYPE_RECTANGLE).show(); } */ onWindowFocusChanged(true); } }, 550); SharedPreferences.Editor editor = sp.edit(); editor.putBoolean("isFirst", false); editor.commit(); } else { handler.postDelayed(new Runnable() { @Override public void run() { onWindowFocusChanged(false); } }, 2000); } } private void init() { mCollect = (ImageView) findViewById(R.id.collect); mTitleTv = (TextView) findViewById(R.id.title); mListView = (ListView) findViewById(R.id.list); mList = new ArrayList<>(); for (int i = 0; i < 50; i++) { mList.add("Item:" + i); } mListView.setAdapter(new ListAdapter(mList,getApplicationContext())); mListView.setOnScrollListener(this); } @Override protected void onResume() { super.onResume(); } /**如果剛啟動Activity時就要計算這些數據,最好在 onWindowFocusChanged 函數中進行, 否則得到的某些數據可能是錯誤的,比如,應用區域高寬的獲取 */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) {} @Override public void onScroll(final AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if(firstVisibleItem >= 6 && NewbieGuideManager.isNeverShowed(this, NewbieGuideManager.TYPE_LIST) && !isShow) { isShow = true; mListView.smoothScrollToPosition(6); mListView.postDelayed(new Runnable() { @Override public void run() { mListView.setSelection(6); mListView.post(new Runnable() { @Override public void run() { new NewbieGuideManager(MainActivity.this, NewbieGuideManager.TYPE_LIST).addView(view.getChildAt(0) .findViewById(R.id.logo), HoleBean.TYPE_RECTANGLE).show(); } }); } }, 200); } } @Override protected void onDestroy() { super.onDestroy(); //todo 下面代碼需要刪掉 /** SharedPreferences.Editor editor = getSharedPreferences("newbie_guide", MODE_PRIVATE).edit(); editor.putBoolean("newbie_guide" + NewbieGuideManager.TYPE_LIST, true); editor.putBoolean("newbie_guide" + NewbieGuideManager.TYPE_COLLECT, true); editor.apply(); */ } public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if(NewbieGuideManager.isNeverShowed(this, NewbieGuideManager.TYPE_COLLECT)) { new NewbieGuideManager(this, NewbieGuideManager.TYPE_COLLECT).addView(mCollect, HoleBean.TYPE_CIRCLE).addView(mTitleTv, HoleBean.TYPE_RECTANGLE).show(); } }}
這裡的邏輯都在 ui操作裡面 我們操作過了app 就改變布爾值 來 通過onWindowFocusChanged(true或者false)來決定我們是不是需要 在去拉起浮層view。new NewbieGuideManager(this, NewbieGuideManager.TYPE_COLLECT).addView(mCollect, HoleBean.TYPE_CIRCLE).addView(mTitleTv, HoleBean.TYPE_RECTANGLE).show();這個方法 我們可以覺得顯示view的對象,樣式,和我們需要顯示的高亮區域等。
通過這個 能對 view 和輕量級存儲,listview 像素計算 有不錯的進步的。
微信附近的人是一個可以看到周邊人也在玩這個功能的人。有朋友說他可以看到別人,但是別人看不到他,這是為什麼呀?我的微信能看到附近的人,但附近的人看不見我,怎麼
平時對牛逼動畫,高級UI,都深入的不多!近日,某頭條,推了一個android技術類視頻(平時在頭條關注技術比較多),講的是加載動畫效果,是動腦學院講的公開課,160分鐘,
最近要面試了 沒 時間玩了.看了下安卓icp ,說實話一直沒過aidl這東東 發現網上帖子好多 下載的卻好少 .好吧自己玩了一個 留下做個備注吧. 服務端輸入文
手機下載了一些APP,發現現在仿win8的主界面越來越多,在大家見慣了類GridView或者類Tab後,給人一種耳目一新的感覺。今天在eoe上偶然發現已經有人實現了這個功