现在很多第三方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不用说,大家都是上面是个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 /* 圆角半径 */) {
return getRoundCornerDrawable(ctx,
mContext.getResources().getDrawable(resId), roundPX);
}
private Drawable getRoundCornerDrawable(Context ctx, Drawable drawable,
float roundPX /* 圆角半径 */) {
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);
}
}
下载地址:就这么多了,~~完~~