时间:2021-08-14 08:32:32 | 栏目:Android代码 | 点击:次
现在很多第三方Launcher((如360Launcher,GoLauncher)带有iphone主题,相信玩Android的人大都知道。
本例实现仿iphone主题的launcher的冰山一角。如下图:
从效果看,大概就能猜出用什么控件类(支持左右滑动的控件类+GridView),支持左右滑动的控件类,有很多了比如常用的Gallery,ViewPager,ViewFlipper,ViewFlow等等,本例自定义继承ViewGroup的。看过launcher源码的人应该都知道 有个Workspace类继承ViewGroup实现主菜单的。
闲话不多说了!
主布局:main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <com.xyz.workspace.Workspace android:id="@+id/workspace" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <com.xyz.workspace.PageIndicator android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="20dip" /> </RelativeLayout>
第一个自定义类Workspace就是实现左右滑动的,第二个类PageIndicator做指示器用。
Workspace.java
package com.xyz.workspace; import java.util.List; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.Scroller; public class Workspace extends ViewGroup { private static final String TAG = "Workspace"; private Scroller mScroller; private VelocityTracker mVelocityTracker; private static final int DEFAULT_SCREEN = 0; private static final int TOUCH_STATE_REST = 0; private static final int TOUCH_STATE_SCROLLING = 1; private static final int SNAP_VELOCITY = 600; public static final int APP_PAGE_SIZE = 16; private int mCurScreen; private int mTouchState = TOUCH_STATE_REST; private int mTouchSlop; private float mLastMotionX; private float mLastMotionY; private OnViewChangedListener mOnViewChangedListener; public Workspace(Context context, AttributeSet attrs) { this(context, attrs, 0); // TODO Auto-generated constructor stub } public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub mScroller = new Scroller(context); mCurScreen = DEFAULT_SCREEN; mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub if (changed) { int childLeft = 0; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int width = MeasureSpec.getSize(widthMeasureSpec); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { throw new IllegalStateException( "ScrollLayout only canmCurScreen run at EXACTLY mode!"); } final int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode != MeasureSpec.EXACTLY) { throw new IllegalStateException( "ScrollLayout only can run at EXACTLY mode!"); } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); } scrollTo(mCurScreen * width, 0); } public void snapToDestination() { final int screenWidth = getWidth(); final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth; snapToScreen(destScreen); } public void snapToScreen(int whichScreen) { whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); if (getScrollX() != (whichScreen * getWidth())) { final int delta = whichScreen * getWidth() - getScrollX(); mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2); mCurScreen = whichScreen; invalidate(); } if (mOnViewChangedListener != null) { mOnViewChangedListener.onChange(getChildCount(), whichScreen); } } public void setToScreen(int whichScreen) { whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); mCurScreen = whichScreen; scrollTo(whichScreen * getWidth(), 0); } public int getCurScreen() { return mCurScreen; } @Override public void computeScroll() { // TODO Auto-generated method stub if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); final int action = event.getAction(); final float x = event.getX(); final float y = event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } mLastMotionX = x; break; case MotionEvent.ACTION_MOVE: int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; scrollBy(deltaX, 0); break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000); int velocityX = (int) velocityTracker.getXVelocity(); if (velocityX > SNAP_VELOCITY && mCurScreen > 0) { snapToScreen(mCurScreen - 1); } else if (velocityX < -SNAP_VELOCITY && mCurScreen < getChildCount() - 1) { snapToScreen(mCurScreen + 1); } else { snapToDestination(); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mTouchState = TOUCH_STATE_REST; break; case MotionEvent.ACTION_CANCEL: mTouchState = TOUCH_STATE_REST; break; } return true; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { return true; } final float x = ev.getX(); final float y = ev.getY(); switch (action) { case MotionEvent.ACTION_MOVE: final int xDiff = (int) Math.abs(mLastMotionX - x); if (xDiff > mTouchSlop) { mTouchState = TOUCH_STATE_SCROLLING; } break; case MotionEvent.ACTION_DOWN: mLastMotionX = x; mLastMotionY = y; mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mTouchState = TOUCH_STATE_REST; break; } return mTouchState != TOUCH_STATE_REST; } public void setOnViewChangedListener(OnViewChangedListener l) { mOnViewChangedListener = l; } public interface OnViewChangedListener { public void onChange(int cnt, int index); } }
PageIndicator.java:
package com.xyz.workspace; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; public class PageIndicator extends LinearLayout { private Context mContext; public PageIndicator(Context ctx) { super(ctx); // TODO Auto-generated constructor stub mContext = ctx; } public PageIndicator(Context ctx, AttributeSet attrs) { super(ctx, attrs); // TODO Auto-generated constructor stub mContext = ctx; } public void setIndication(int cnt, int index) { if (index < 0 || index > cnt) index = 0; removeAllViews(); for (int i = 0; i < cnt; i++) { ImageView iv = new ImageView(mContext); iv.setImageResource(index == i ? R.drawable.indicator_current : R.drawable.indicator); if (i != 0 || i != cnt - 1) { iv.setPadding(4, 0, 4, 0); } addView(iv); } } }
这两个类的作用上面已经说了,有什么看不明白的欢迎提问,或自行google。
ViewGroup实现好了,剩下就是实现GridView显示系统所有app,主要工作也就是实现GridView的适配器---GridAdapter
package com.xyz.workspace; import java.util.List; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import static com.xyz.workspace.Workspace.APP_PAGE_SIZE; public class GridAdapter extends BaseAdapter implements OnClickListener { private Context mContext; private int mPageIndex; private List<ResolveInfo> mPackagesInfo; public GridAdapter(Context context, List<ResolveInfo> listInfo, int page) { mContext = context; mPackagesInfo = listInfo; mPageIndex = page; } @Override public int getCount() { // TODO Auto-generated method stub int size = mPackagesInfo.size(); return size / APP_PAGE_SIZE > 0 && size - (APP_PAGE_SIZE * (mPageIndex + 1)) > 0 ? APP_PAGE_SIZE : size % APP_PAGE_SIZE; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return mPackagesInfo.get(APP_PAGE_SIZE * mPageIndex + 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 if (convertView == null) { convertView = new AppItem(mContext, (ResolveInfo) getItem(position)); } convertView.setOnClickListener(this); convertView.setTag(Integer.valueOf(position)); return convertView; } /** 点击启动app **/ @Override public void onClick(View v) { // TODO Auto-generated method stub int pos = (Integer) v.getTag(); ResolveInfo info = (ResolveInfo) getItem(pos); Intent i = new Intent(Intent.ACTION_MAIN); i.addCategory(Intent.CATEGORY_LAUNCHER); i.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name)); mContext.startActivity(i); } }
GridView的每个item不用说,一看就知道是一个LinearLayout上面是个ImageView,下面一个TextView了。我把它封装了下---AppItem:
package com.xyz.workspace; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Bitmap.Config; import android.graphics.PorterDuff.Mode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; public class AppItem extends RelativeLayout { private Context mContext; private ImageView mAppIcon; private TextView mAppName; private ResolveInfo mAppInfo; private PackageManager mPackageManager; public AppItem(Context context) { super(context); mContext = context; mPackageManager = context.getPackageManager(); LayoutInflater.from(context).inflate(R.layout.app_item, this); mAppIcon = (ImageView) findViewById(R.id.icon); mAppName = (TextView) findViewById(R.id.app_name); } public AppItem(Context context, ResolveInfo info) { this(context); mAppInfo = info; show(); } private void show() { String packageName = mAppInfo.activityInfo.packageName; String appName = mAppInfo.activityInfo.loadLabel(mPackageManager) .toString(); if (appName.equals("拨号")) { mAppIcon.setImageResource(R.drawable.com_android_phone); } else if (packageName.equals("com.android.contacts")) { mAppIcon.setImageResource(R.drawable.com_android_contacts); } else if (packageName.equals("com.android.mms")) { mAppIcon.setImageResource(R.drawable.com_android_mms); } else if (packageName.equals("com.android.music")) { mAppIcon.setImageResource(R.drawable.com_android_music); } else if (packageName.equals("com.android.browser")) { mAppIcon.setImageResource(R.drawable.com_android_browser); } else if (packageName.equals("com.android.settings")) { mAppIcon.setImageResource(R.drawable.com_android_settings); } else if (packageName.equals("com.android.email")) { mAppIcon.setImageResource(R.drawable.com_android_email); } else if (packageName.equals("com.android.calendar")) { mAppIcon.setImageResource(R.drawable.com_android_calendar); } else if (packageName.equals("com.android.calculator2")) { mAppIcon.setImageResource(R.drawable.com_android_calculator2); } else if (packageName.equals("com.android.deskclock")) { mAppIcon.setImageResource(R.drawable.com_android_deskclock); } else if (packageName.equals("com.android.camera")) { mAppIcon.setImageResource(R.drawable.com_android_camera); } else if (packageName.equals("com.android.soundrecorder")) { mAppIcon.setImageResource(R.drawable.com_android_soundrecorder); } else if (packageName.equals("com.tencent.mobileqq")) { mAppIcon.setImageResource(R.drawable.com_tencent_qq); } else if (packageName.equals("com.tencent.mm")) { mAppIcon.setImageResource(R.drawable.com_tencent_mm); } else if (packageName.equals("com.tencent.mtt")) { mAppIcon.setImageResource(R.drawable.com_tencent_mtt); } else if (packageName.equals("com.sina.weibo")) { mAppIcon.setImageResource(R.drawable.com_sina_weibo); } else if (packageName.equals("com.sds.android.ttpod")) { mAppIcon.setImageResource(R.drawable.com_sds_android_ttpod); // //////////////////////////////////////////////////////////////// } else if (packageName.equals("com.youdao.dict")) { mAppIcon.setImageResource(R.drawable.com_youdao_dict); } else { mAppIcon.setImageDrawable(getRoundCornerDrawable(mContext, mAppInfo.activityInfo.loadIcon(mPackageManager), 20)); } mAppName.setText(appName); } private Drawable getRoundCornerDrawable(Context ctx, int resId, float roundPX /* <span style="font-size:14px;">圆角半径 </span>*/) { return getRoundCornerDrawable(ctx, mContext.getResources().getDrawable(resId), roundPX); } private Drawable getRoundCornerDrawable(Context ctx, Drawable drawable, float roundPX /* <span style="font-size:14px;">圆角半径 </span>*/) { int w = ctx.getResources() .getDimensionPixelSize(R.dimen.app_icon_width); int h = w; Bitmap bitmap = Bitmap .createBitmap( w, h, drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, w, h); drawable.draw(canvas); int width = bitmap.getWidth(); int height = bitmap.getHeight(); Bitmap retBmp = Bitmap.createBitmap(width, height, Config.ARGB_8888); Canvas can = new Canvas(retBmp); final int color = 0xff424242; final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, width, height); final RectF rectF = new RectF(rect); paint.setColor(color); paint.setAntiAlias(true); can.drawARGB(0, 0, 0, 0); can.drawRoundRect(rectF, roundPX, roundPX, paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); can.drawBitmap(bitmap, rect, rect, paint); return new BitmapDrawable(retBmp); } }
注意咯,show函数就是替换显示对应iphone里app的图标(来源反编译iphone主题的launcher或锁屏),利用 包名 判断是哪个应用再换上对应图标,例如com.android.mms---信息,com.android.contacts---联系人,这里有个疑问,为什么phone模块的package_name的也是com.android.contacts,有人知道么?谢谢啦!
AppItem引用一个布局:
app_item.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="@dimen/app_icon_width" android:layout_height="@dimen/app_icon_height" android:gravity="center" android:orientation="vertical" > <ImageView android:id="@+id/icon" android:layout_width="@dimen/app_icon_width" android:layout_height="@dimen/app_icon_width" android:layout_gravity="center_horizontal" /> <TextView android:id="@+id/app_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:ellipsize="marquee" android:maxWidth="@dimen/app_icon_height" android:singleLine="true" android:textColor="@android:color/white" android:textSize="12sp" /> </LinearLayout>
主Activity就是获取所有app信息及初始化界面,
MainActivty.java:
package com.xyz.workspace; import java.util.List; import com.xyz.workspace.Workspace.OnViewChangedListener; import android.app.Activity; import android.content.Intent; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.view.Gravity; import android.widget.GridView; import static com.xyz.workspace.Workspace.APP_PAGE_SIZE; public class MainActivity extends Activity implements OnViewChangedListener { private Workspace mWorkspace; private PageIndicator mIndicator; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mWorkspace = (Workspace) findViewById(R.id.workspace); mIndicator = (PageIndicator) findViewById(R.id.indicator); List<ResolveInfo> apps = loadApps(); for (int i = 0; i < Math.ceil(1.0f * apps.size() / APP_PAGE_SIZE); i++) { GridView grid = new GridView(this); grid.setNumColumns(4); grid.setHorizontalSpacing(10); grid.setVerticalSpacing(40); grid.setPadding(30, 50, 30, 20); grid.setGravity(Gravity.CENTER); grid.setAdapter(new GridAdapter(this, apps, i)); mWorkspace.addView(grid); } mWorkspace.setOnViewChangedListener(this); mIndicator.setIndication(mWorkspace.getChildCount(), 0); } private List<ResolveInfo> loadApps() { Intent i = new Intent(Intent.ACTION_MAIN, null); i.addCategory(Intent.CATEGORY_LAUNCHER); return getPackageManager().queryIntentActivities(i, 0); } @Override public void onChange(int cnt, int index) { // TODO Auto-generated method stub mIndicator.setIndication(cnt, index); } }
源码下载:android仿iphone主题之主菜单