酷狗app高仿系列

好。。。从今天开始写博客。

下了下酷狗发现很不错啊,交互流畅,ui美观,那就从它开刀。先来个高仿篇。。。。
这是它的侧滑效果
下面是首页,画了框的就是感兴趣的地方v
这是它的首页,是不是赞,更赞的不是背景图,是他的框架

好咱们先分析下他的结构,它的侧滑更qq差不多,就是多了个耳朵view在动,
还有就是在右边(我擦。。。是个人都知道的区别)。那么分析如果没有那个耳朵view再动是不是很简单啊,
用脚趾一想自定viewgroup,重写LinearLayout,重写HrizontalScrollview。。。。
是不是有很多方案。但是多了一个耳朵view LinearLayout就不太合适了。
因为不好布局是吧,重写viewGroup又太麻烦,那咱还是拿简单的HrizontalScrollview来开刀吧。
但是HrizontalScrollview可选的子view又是个要思考下的问题,
因为他不但有FrameLayout的功能还有灵活布置位置的功能。**

第一次使用markdown,有点生,擦 就放这里吧

<!--这是侧滑的horizontalScrollview-->
<com.example.kugou.view.MenuHorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/menu_horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true"
    tools:context="com.example.kugou.MainActivity" >

    <!--这是最外层的relativelayout ,为什么要自定义呢,博客等下会说-->
    <com.example.kugou.view.MenuRelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#55ff0000" >

        <!--会动的那个耳朵view-->
        <ImageView
            android:id="@id/main_menu_icon_id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/main_content_id"
            android:src="@drawable/ic_launcher" />

        <!--内容区域-->
        <RelativeLayout
            android:id="@id/main_content_id"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#ccc" >


        </RelativeLayout>

        <!--菜单区域-->
        <LinearLayout
            android:id="@id/main_menu_id"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_toRightOf="@id/main_content_id"
            android:orientation="vertical" >


        </LinearLayout>
    </com.example.kugou.view.MenuRelativeLayout>

</com.example.kugou.view.MenuHorizontalScrollView>

这里有好几个id是定义在res下的因为这样方便侧滑控件的重复使用,
这个layout的作用 起到一个框架的作用,搭了一个架子,
相当于现在做房子做了个水泥柱,没有砌砖。
哦。。差点忘记了,id定义在res还有个作用是那个耳朵view是在relativelayout的第一个位置的,
如果直接写@+id的话编译器是会报错的。
好,该我们的侧滑控件MenuHorizontalScrollView登场了。。。。
首先来分析下这侧滑控件要做的事情:
1.能够滑动,且能够到一定位置时滚动到指定的位置,即menu的open和close。
(废话,侧滑没有这个功能还叫侧滑吗。。。)
2.要解决垂直滚动和水平滚动的冲突
暂时先是这两个问题
好,开始我们的coding



public class MenuHorizontalScrollView extends HorizontalScrollView {

    /*这里设置menu为屏幕宽4/5*/
    private final float menuScale = 4 / 5f;
    private final int INVALID_POINTER = -1;
    private int screenWidth;
    private int menuViewWidth;
    private int iconViewWidth;
    private boolean isOpen;
    /*拿到contentChild和menuChild  好做一些动画*/
    private ViewGroup contentChild;
    private ViewGroup menuChild;
    private View iconView;
    /*解决滑动冲突东东*/
    private int mActivePointerId;
    private float mInitialMotionX;
    private float mInitialMotionY;


    public MenuHorizontalScrollView(Context context) {
        this(context, null);
        // TODO Auto-generated constructor stub
    }

    public MenuHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        // TODO Auto-generated constructor stub
    }

    public MenuHorizontalScrollView(Context context, AttributeSet attrs,
                                    int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        initView(context);
    }

    private void initView(Context context) {
        // TODO Auto-generated method stub
        screenWidth = context.getResources().getDisplayMetrics().widthPixels;
        menuViewWidth = (int) (screenWidth * menuScale);
    }

    /**
     * 因为我们的menu是固定大小的 是吧 所以我们要给他设置下
     * 再测量之前设置下layoutParams是可以直接起作用的
     * 因为在测量的时候来拿出layoutParams
     * 相信玩过自定义view都都看到过 是吧
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub

        ViewGroup rlChildview = (ViewGroup) getChildAt(0);
        rlChildview.getLayoutParams().width = screenWidth + menuViewWidth;
        contentChild = (ViewGroup) rlChildview
                .findViewById(R.id.main_content_id);
        contentChild.getLayoutParams().width = screenWidth;

        menuChild = (ViewGroup) rlChildview.findViewById(R.id.main_menu_id);
        menuChild.getLayoutParams().width = menuViewWidth;

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        contentChild.setPivotX(screenWidth);
        contentChild.setPivotY(contentChild.getMeasuredHeight() / 2);

        iconViewWidth = iconView.getMeasuredWidth();

    }

    /*这个方法就是在xmlParse后调用的 可以找到我们的一些控件*/
    @Override
    protected void onFinishInflate() {
        // TODO Auto-generated method stub
        super.onFinishInflate();

        iconView = findViewById(R.id.main_menu_icon_id);
    }

    /* 解决垂直和水平滑动的问题*/
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                int index = MotionEventCompat.getActionIndex(ev);
                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
                if (mActivePointerId == INVALID_POINTER) {
                    break;
                }
                mInitialMotionX = MotionEventCompat.getX(ev, index);
                mInitialMotionY = MotionEventCompat.getY(ev, index);

                break;
            case MotionEvent.ACTION_MOVE:
                int lastIndex = MotionEventCompat.findPointerIndex(ev,
                        mActivePointerId);
                final float lastX = MotionEventCompat.getX(ev, lastIndex);
                final float lastY = MotionEventCompat.getY(ev, lastIndex);
                final float destX = Math.abs(lastX - mInitialMotionX);
                final float destY = Math.abs(lastY - mInitialMotionY);
                if (destX > destY) {
                    if (!isOpen && lastX - mInitialMotionX > 0) {
                        return false;
                    }
                    /*这里一定不能直接返回true  因为在父类要做一些判断和初始化变量不然没有效果了*/
                    return super.onInterceptTouchEvent(ev);
                } else {
                    return false;
                }
            case MotionEvent.ACTION_UP:
                break;
        }

        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 因为本身就是horizontalScrollview 就代用滚动
     * 所以我们只需要判断一下actionup 是否要打开还是关闭menu
     * @param ev
     * @return
     */


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            if (getScrollX() > menuViewWidth / 2) {
                this.smoothScrollTo(menuViewWidth, 0);
                isOpen = true;
            } else {
                this.smoothScrollTo(0, 0);
                isOpen = false;
            }
            return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 这里呢就是做一些动画了
     */

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        // TODO Auto-generated method stub
        super.onScrollChanged(l, t, oldl, oldt);

        float scale = l * 1.0f / menuViewWidth;

        float menuScale = 0.8f + 0.2f * scale;

        float contentScale = 1.0f - 0.2f * scale;

        /* menu change */
        PropertyValuesHolder menuScaleX = PropertyValuesHolder.ofFloat(
                "scaleX", menuScale);
        PropertyValuesHolder menuScaleY = PropertyValuesHolder.ofFloat(
                "scaleY", menuScale);
        PropertyValuesHolder menuTranslateY = PropertyValuesHolder.ofFloat(
                "translationX", -(1 - scale) * menuViewWidth * 0.2f);

        ObjectAnimator menuOA = ObjectAnimator.ofPropertyValuesHolder(
                menuChild, menuScaleX, menuScaleY, menuTranslateY);
        menuOA.setDuration(0);
        menuOA.start();

        /* content change */

        PropertyValuesHolder contentScaleX = PropertyValuesHolder.ofFloat(
                "scaleX", contentScale);
        PropertyValuesHolder contentScaleY = PropertyValuesHolder.ofFloat(
                "scaleY", contentScale);
        ObjectAnimator contentOA = ObjectAnimator.ofPropertyValuesHolder(
                contentChild, contentScaleX, contentScaleY);
        contentOA.setDuration(0);
        contentOA.start();
        /* menu icon change */
        ObjectAnimator iconOA = ObjectAnimator.ofFloat(iconView,
                "translationX", -scale * iconViewWidth * 0.5f);
        iconOA.setDuration(0);
        iconOA.start();
    }

    /*暂时给哪儿耳朵View使用,控制菜单的显示和隐藏*/
    public void toggleMenu() {
        if (isOpen) {
            this.smoothScrollTo(0, 0);
        } else {
            this.smoothScrollTo(menuViewWidth, 0);
        }
        isOpen = !isOpen;
    }


}

是不是很简单啊。就那么几十行代码。侧滑已经很多了。相信大家很容易明白。
不然拦截没有效果,大家可以去看下HorizontalScrollView的源码。
至于onScrollChanged方法里面的动画变化处理,
因为是模仿不是正规的开发,所以就直接使用了属性动画,
要是要兼容的话可以使用开源库nineoldandroids.
来看下效果:

这里写图片描述

是不是比较坑,完全不是我们想要的啊。该上的没上,该下的没下(到底是谁上谁下呢,,,,,我去 跑腿了。。。)
我们来分析下原因。返回到上面我们的布局文件,你会发现什么,用火眼看下,是不是不坑,是我们的代码写的坑是吧。前人挖坑后人填坑,前人种树后人乘凉,我们希望我们新进的公司的前辈都是种树的不是挖坑的,我们好乘凉喝茶看妹子,打豆豆啊,不然天天填坑,就不能愉快的下班找妹子约会了。。。嗨嗨。。中风了
所以我们要自定义RelativeLayout 是吧,前面已经说了。好开始我们的改造view
public MenuRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        setChildrenDrawingOrderEnabled(true);
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        // TODO Auto-generated method stub

        return childCount - i - 1;
    }

就两句代码句搞定了,改变下view的绘制顺序,好吧,这样我们的侧滑就完成了,哎。。。这天气是中暑的节奏。。先去喝杯水。

接下来呢是重头戏,上面的都是铺贴,房基是吧。
那么开始我们的内容区域吧。。。。
我们先来分析下哈

这里写图片描述

他不是两个activity而是在一个activity的一个容器里,是吧,
不然,下面的那个播放音乐的就不好搞了。
我们来思考下,这个怎么做好呢。。。
1.用一个FrameLayout作为容器,然后不断的添加view?
看来是个不错的选择,那么我们就先FrameLayout试试。
还是使用LayoutTransition呢?
3.滑动返回又怎么处理呢?直接自定义?还是v4包里的viewDragHelp?
进过试验和考虑我选择了使用FrameLayout作为容器
动画这一块使用预备容器的做法,因为“在addview是添加时自己加动画?
还是使用LayoutTransition呢”都不理想,在低端机下回闪一下体验不好。
滑动使用viewdraghelp可以减少很多代码量啊。
使用view作为activity一样的用,那么我们必须封装一下啊 是吧,
不然每次都一大堆重复代码,还能不能愉快的下班了。

定义一个接口,
public interface IViewPager {
    View attachView(MenuContentFrameLayout rootView);

}

再来一个基类

public abstract class BaseViewPager implements IViewPager {

    protected View contentView;
    protected Context mContext;
    protected MenuContentFrameLayout rootView;

    public BaseViewPager(Context mContext, MenuContentFrameLayout rootView) {
        // TODO Auto-generated constructor stub
        this.mContext = mContext;
        contentView = attachView(rootView);
        this.rootView = rootView;

        initView();
        initValue();
        setListener();
    }

    protected abstract void initView();

    protected abstract void initValue();

    protected abstract void setListener();

}

那首页view代码来分析下

public class MainPager extends BaseViewPager {

    private Button goBtn;

    public MainPager(Context context, MenuContentFrameLayout rootView) {
        super(context, rootView);
        // TODO Auto-generated constructor stub
    }

    @Override
    public View attachView(MenuContentFrameLayout rootView) {
        // TODO Auto-generated method stub
        return LayoutInflater.from(mContext).inflate(
                R.layout.main_content_layout, rootView, true);
    }

    @Override
    protected void initView() {
        // TODO Auto-generated method stub
        goBtn = (Button) contentView.findViewById(R.id.btn_go);
    }

    @Override
    protected void initValue() {
        // TODO Auto-generated method stub

    }

    @Override
    protected void setListener() {
        // TODO Auto-generated method stub

        goBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                new FriendListPager(mContext, rootView);
            }
        });

    }

}这里写代码片
public class MainActivity extends Activity {
    private MenuHorizontalScrollView menuHroizontalScrollView;
    private ImageView menuIconView;
    private MenuContentFrameLayout contentFl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initValue();
        initListener();

    }

    private void initView() {
        // TODO Auto-generated method stub
        menuHroizontalScrollView = (MenuHorizontalScrollView) findViewById(R.id.menu_horizontal);
        menuIconView = (ImageView) findViewById(R.id.main_menu_icon_id);
        contentFl = (MenuContentFrameLayout) findViewById(R.id.content_fl);

    }

    private void initValue() {
        // TODO Auto-generated method stub
        new MainPager(this, contentFl);

    }

    private void initListener() {
        // TODO Auto-generated method stub
        menuIconView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                menuHroizontalScrollView.toggleMenu();
            }
        });
    }
}

看到没有 我们的mainactivity使用new MainPager(this, contentFl);
就可以把mainViewpager添加到容器中了,
有点犯困,这里写的有点乱。最后附上所有代码,估计看大代码就情绪了,

最后上我们最核心的类

public class MenuContentFrameLayout extends FrameLayout {
    /*重点滑动view*/
    private ViewDragHelper mDragHelper;

    private static final int MIN_FLING_VELOCITY = 400;

    private int mActivePointerId;
    private final int INVALID_POINTER = -1;
    private float mInitialMotionX;
    private float mInitialMotionY;

    private DisplayMetrics displayMetrics;

    /*保存添加的view*/
    private ArrayList<View> stackViewList;

    /*回收的view*/
    private ArrayList<View> recycleViewList;

    /*做动画的辅助view*/
    private ContentSubFrameLayout subContentLayout;

    public MenuContentFrameLayout(Context context) {
        this(context, null);
        // TODO Auto-generated constructor stub
    }

    public MenuContentFrameLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        // TODO Auto-generated constructor stub
    }

    public MenuContentFrameLayout(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub

        initView(context);
    }

    private void initView(Context context) {
        // TODO Auto-generated method stub
        mDragHelper = ViewDragHelper.create(this, 1.0f, new MyCallback());
        displayMetrics = context.getResources().getDisplayMetrics();
        final float minVel = MIN_FLING_VELOCITY * displayMetrics.density + 0.5f;
        mDragHelper.setMinVelocity(minVel);

        stackViewList = new ArrayList<View>();
        recycleViewList = new ArrayList<View>();

    }

    /**
     * 这里处理滑动返回和滑动冲突处理
     * @param ev
     * @return
     */

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub

        int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            int index = MotionEventCompat.getActionIndex(ev);
            mActivePointerId = MotionEventCompat.getPointerId(ev, index);
            if (mActivePointerId == INVALID_POINTER) {
                break;
            }
            mInitialMotionX = MotionEventCompat.getX(ev, index);
            mInitialMotionY = MotionEventCompat.getY(ev, index);

            break;
        case MotionEvent.ACTION_MOVE:
            int lastIndex = MotionEventCompat.findPointerIndex(ev,
                    mActivePointerId);
            final float lastX = MotionEventCompat.getX(ev, lastIndex);
            final float lastY = MotionEventCompat.getY(ev, lastIndex);
            final float destX = Math.abs(lastX - mInitialMotionX);
            final float destY = Math.abs(lastY - mInitialMotionY);
            if (destX > destY) {
                return mDragHelper.shouldInterceptTouchEvent(ev);
            } else {
                return false;
            }
        case MotionEvent.ACTION_UP:
            break;
        }

        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        mDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    public void computeScroll() {
        // TODO Auto-generated method stub
        if (mDragHelper.continueSettling(true)) {
            invalidate();
        }
    }

    /**
     * v4包里的滑动回调
     */
    private class MyCallback extends ViewDragHelper.Callback {

        /*是否要捕获*/
        @Override
        public boolean tryCaptureView(View arg0, int arg1) {
            // TODO Auto-generated method stub
            return true;
        }

        /*水平滑动距离*/
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            // TODO Auto-generated method stub
            if (getChildCount() == 1)
                left -= dx * 2 / 3;
            if (left < 0)
                left = 0;
            return left;
        }

        /*当手机松开时*/
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            // TODO Auto-generated method stub

            if (getChildCount() == 1) {
                mDragHelper.settleCapturedViewAt(0, releasedChild.getTop());
            } else {
                final float offset = releasedChild.getLeft() * 1.0f
                        / releasedChild.getWidth();

                mDragHelper.settleCapturedViewAt(xvel > 0 || xvel == 0
                        && offset >= 0.5 ? releasedChild.getWidth() : 0,
                        releasedChild.getTop());
            }
            invalidate();
        }

        /*位置变化时,这里可以做动画*/
        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {
            // TODO Auto-generated method stub
            if (getChildCount() > 1) {


                if (left >= changedView.getWidth()) {
                    removeView(changedView);
                    stackViewList.remove(stackViewList.size() - 1);

                    if (recycleViewList.size() > 0) {
                        addView(recycleViewList.get(recycleViewList.size() - 1),
                                0);
                        recycleViewList.remove(recycleViewList.size() - 1);

                    }
                }
            }
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            // TODO Auto-generated method stub
            return child.getWidth();
        }

    }

    @Override
    public void addView(View child, android.view.ViewGroup.LayoutParams params) {
        // TODO Auto-generated method stub
        if (child instanceof ContentSubFrameLayout) {
            super.addView(child, params);
        } else {

            if (stackViewList.size() == 0) {
                super.addView(child, params);
            } else {

                if (stackViewList.size() >= 2) {
                    View oneChildView = getChildAt(0);
                    recycleViewList.add(oneChildView);
                    removeView(oneChildView);
                }

                /*添加一个辅助容器来作出现动画,避免闪一下问题*/
                final ContentSubFrameLayout subContentLayout = new ContentSubFrameLayout(
                        getContext());
                final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT,
                        FrameLayout.LayoutParams.MATCH_PARENT);
                layoutParams.setMargins(displayMetrics.widthPixels, 0, 0, 0);
                addView(subContentLayout, layoutParams);

                subContentLayout.addView(child, params);

                /*处理动画*/
                ObjectAnimator subOA = ObjectAnimator.ofInt(subContentLayout,
                        "left", displayMetrics.widthPixels, 0);
                subOA.setDuration(600);
                subOA.start();

                subOA.addListener(new AnimatorListenerAdapter() {

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        // TODO Auto-generated method stub
                        final FrameLayout.LayoutParams layoutParams = (LayoutParams) subContentLayout
                                .getLayoutParams();
                        layoutParams.leftMargin = 0;
                    }
                });
            }
            stackViewList.add(child);
        }
    }

}

代码里都有注释,最主要的是出现动画哪里,都加了一个辅助容器,
不然会出现闪一下的问题,自己可以试验下。

还加了一个缓存view,就是当view添加到多余三个时要把底部的view从容器移除,
不然侧滑菜单是会很卡顿,因为都看不到了,侧滑时还一直draw,也没有意思了,
是吧,所以就移除把,当要的时候再添加上去。界面的话没有去抠图,
程序员要的是核心代码和思想不是几个图片,是吧。
好了今天就到这里了。。。。困到不行啊,,眯会去。。。

哪里上传代码啊。。。晕

第一次写博客,哎,,第一次真是痛啊。后续持续更新,今天就先到这了

源代码

Redrain仿酷狗音乐播放器     这篇文章只是对开源的说明!关于这个Redrain音乐盒的发布程序的说明和使用方法,见《Redrain仿酷狗音乐播放器开发完毕,发布测试程序》。    今天,我把这个项目的源代码上传。包括了可以编译工程所需的所有代码文件,已经软件的布局文件,但是没有包括软件需要的素材,这也是为了避免引起与酷狗播放器的版权问题。        我在这个项目中使用的是 UiLib 库而不是 DuiLib 库,UiLib库是DuiLib库的扩展版本,增加了一些动画控件的支持,扩展了部分控件,但是核心代码并没有改变,与DuiLib使用方法完全一样,也可以用UiLib库直接编译使用 DuiLib 库编写的代码。项目中的 UiLib 是我为了适应仿酷狗而专门修改过的,也修复过必要的 bug,所以如果你使用原版的 DuiLib 或者 UiLib 库去编译这个功能,最终的程序效果和我发布的不一样。关于 bug的修复请看博客中更早期的文章。我自己使用并且维护的DuiLib库和UiLib库的下载地址见博客:《Redrain个人维护并使用的DuiLib和UiLib库源码下载地址》       在这个项目源码中,同时包含了webkit内核浏览器控件、音乐播放类、换肤功能、拖拽功能、菜单等等。这个菜单的功能如下:         1、可以展现多级菜单         2、可内嵌自定义控件,并且控件可以向主窗体发送消息,如图的红色叹号就是个按钮控件,可以制作酷狗音乐的托盘菜单的播放暂停按钮和进度控制进度条。         3、菜单拥有阴影效果         4、菜单可以自定义前方显示小图标,并且可以控制图标的大小和是否显示         5、菜单可以根据是否拥有子菜单决定是否显示小箭头         6、菜单可以添加分割线         7、每个菜单项都可以单选和复选的功能         8、优化菜单的xml描述文件,编写方便容易,如果要写一个二级菜单,比如编写图片中的菜单测试4以及他的子菜单,只需如下代码就可以了         9、可以通过键盘的按钮控制菜单的选项         10、每个菜单项的高度宽度是任意调整的 在这个项目中,还有一些未实现的功能,但是我并不打算继续完成这些功能了:      1、退出程序时逐渐缩小的动画特效      2、各个菜单的响应      3、歌词功能(已经有网友做出来了,我就不另外做了)      4、嵌入桌面的桌面歌词 注意 此项目开源代码只是为了学习交流,不可用于商业程序,源码对使用者造成的损失,概不负责! 源码下载地址:点击打开链接 via http://blog.csdn.net/zhuhongshu/article/details/41037875
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值