360项目-08


## 软件管理 ##

- 文件大小的计算

    android.text.Formatter 类可以格式化文件大小
    // 内部存储, 其实就是data目录的容量

        File dataDirectory = Environment.getDataDirectory();
           // 全部
        long totalSpace = dataFile.getTotalSpace();
        // 可用
        long usableSpace = dataFile.getUsableSpace();
        // 已用
        long usedSpace = totalSpace - usableSpace;
        mPdvRom.setTitle("内存: ");
        mPdvRom.setTextLeft(Formatter.formatFileSize(getApplicationContext(), usedSpace) + "已用");
        mPdvRom.setTextRight(Formatter.formatFileSize(getApplicationContext(), freeSpace) + "可用");
        mPdvRom.setProgress((int) (usedSpace * 100f / totalSpace + 0.5f)); // 四舍五入
        // SD卡
        File sdDirectory = Environment.getExternalStorageDirectory();
        // 总空间
        long sdTotalSpace = sdDirectory.getTotalSpace();
        // 剩余空间
        long sdFreeSpace = sdDirectory.getFreeSpace();
        long sdUsedSpace = totalSpace - freeSpace;
        mPdvSD.setTitle("SD卡: ");
        mPdvSD.setTextLeft(Formatter.formatFileSize(getApplicationContext(), sdUsedSpace) + "已用");
        mPdvSD.setTextRight(Formatter.formatFileSize(getApplicationContext(), sdFreeSpace) + "可用");
        mPdvSD.setProgress((int) (sdUsedSpace * 100f / sdTotalSpace+ 0.5f));


## 软件管理的内容部分 ##

- 应用程序列表先简单实现  标准listview带优化的写法

        private class AppAdapter extends BaseAdapter {
            @Override
            public int getCount() {
                return mDatas.size();
            }
            @Override
            public Object getItem(int position) {
                return mDatas.get(position);
            }
            @Override
            public long getItemId(int position) {
                return position;
            }
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ViewHolder holder;
                if (convertView == null) {
                    convertView = View.inflate(getApplicationContext(), R.layout.item_appinfo, null);
                    holder = new ViewHolder();
                    holder.ivIcon = (ImageView) convertView.findViewById(R.id.item_appinfo_iv_icon);
                    holder.tvName = (TextView) convertView.findViewById(R.id.item_appinfo_tv_name);
                    holder.tvInstallPath = (TextView) convertView.findViewById(R.id
                            .item_appinfo_tv_install);
                    holder.tvSize = (TextView) convertView.findViewById(R.id.item_appinfo_tv_size);
                    convertView.setTag(holder);
                } else {
                    holder = (ViewHolder) convertView.getTag();
                }
                // 这里可以使用 getItem 方法获取某个位置对应的对象
                AppInfo info = (AppInfo) getItem(position);
                holder.tvName.setText(info.mName);
                holder.ivIcon.setImageDrawable(info.mIcon);
                holder.tvInstallPath.setText(info.mIsInstallSD ? "SD卡安装" : "手机内存");
                holder.tvSize.setText(Formatter.formatFileSize(getApplicationContext(), info.mSize));
                return convertView;
            }
        }
        static class ViewHolder {
            ImageView ivIcon;
            TextView tvName;
            TextView tvInstallPath;
            TextView tvSize;
        }

-- 对应的JavaBean:

    public class AppInfo {
        public String mName;// 应用的名称
        public String mPackageName;// 应用的包名
        public Drawable mIcon;// 应用图标
        public boolean mIsInstallSD;// 是否安装在sd卡
        public long mSize;// 应用的大小
        public boolean mIsSystem;// 是否是系统程序
    }


## 获取应用程序的信息##

- 创建一个包, engine 或者 business, 写个类, AppInfoProvider

        public class AppInfoProvider {
            public static ArrayList<AppInfo> getAllAppInfo(Context context) {
                PackageManager packageManager = context.getPackageManager();
                // 获取所有的安装包信息, PackageInfo 相当于 manifest 节点
                List<PackageInfo> packages = packageManager.getInstalledPackages(0);
                ArrayList<AppInfo> list = new ArrayList<>();
                for (PackageInfo packageInfo : packages) {
                    AppInfo appInfo = new AppInfo();
                    // 获取包名
                    appInfo.mPackageName = packageInfo.packageName;
                    // 获取应用名称
                    ApplicationInfo applicationInfo = packageInfo.applicationInfo;
                    appInfo.mName = applicationInfo.loadLabel(packageManager).toString();
                    // 获取应用图标
                    appInfo.mIcon = applicationInfo.loadIcon(packageManager);
                    // 获取应用安装包大小
                    String sourceDir = applicationInfo.sourceDir;// data/app/xxx.apk 或者 system/app/xxx.apk
                    //应用安装包
                    appInfo.mSize = new File(sourceDir).length();
                    // info.mSize = Formatter.formatFileSize(context, length);
    
                    // 是否为系统应用
                    if((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo
                            .FLAG_SYSTEM) {
                        appInfo.mIsSystem = true;
                    }else {
                        appInfo.mIsSystem = false;
                    }
    
                    // 是否为外部存储
                    // 应用可以装在sd卡上, 在清单文件根节点中配置 android:installLocation 属性即可
                    if((applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == ApplicationInfo
                            .FLAG_EXTERNAL_STORAGE) {
                        appInfo.mIsInstallSD = true;
                    }else {
                        appInfo.mIsInstallSD = false;
                    }
    
                    list.add(appInfo);
                }
                return list;
            }
        }

注意 flags 和 &, | 运算符的含义

##进度条/include标签的使用##
- 新建一个布局文件 loading:


        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:id="@+id/loading"
            android:orientation="vertical" >
        
            <ProgressBar
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:indeterminateDrawable="@drawable/progress_loading"
                android:indeterminateDuration="600" />
        
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="加载中..."
                android:textColor="#a000"
                android:textSize="16sp" />
        
        </LinearLayout>


在其他布局文件里使用 include 标签引用即可

     <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <ListView
            android:id="@+id/lv_am"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fastScrollEnabled="true" />

        <include
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            layout="@layout/loading" />
    </RelativeLayout>

- 这和直接写在布局文件里效果是一样的, 在代码中, 可以通过id找到相应的控件.

        new Thread() {
            @Override
            public void run() {
                super.run();
                // 模拟耗时操作
                SystemClock.sleep(1000);
                // 填充数据集合s
                infos = AppInfoProvider.getAllAppInfo
                        (getApplicationContext());
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mLlLoading.setVisibility(View.GONE);
                        // 给ListView设置Adapter
                        mLvApp.setAdapter(new AppAdapter());
                    }
                });
            }
        }.start();

- ListView中的条目分组显示
- 把应用信息分成用户应用和系统应用, 在数据加载完成之后分成两个集合

                  mInfos = AppInfoProvider.getAllAppInfo(getApplicationContext());
                mSysInfos = new ArrayList<AppInfo>();
                mUserInfos = new ArrayList<AppInfo>();
                // 区分用户和系统程序 分别添加到两个集合里
                for (AppInfo info : mInfos) {
                    if (info.isSys) {
                        // 系统程序
                        mSysInfos.add(info);
                    } else {
                        // 用户程序
                        mUserInfos.add(info);
                    }
                }
                mInfos.clear();// 清空之前的乱序数据
                // 先添加用户程序 然后系统程序
                mInfos.addAll(mUserInfos);
                mInfos.addAll(mSysInfos);

 

          


##ListView分隔条目/复杂ListView##  重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点

-由于多了两个分隔条目, getCount() 方法返回的数量要加2

        @Override
        public int getCount() {
           return mInfos.size() + 2; // 增加两个分隔条目
        }


- 在 getitem也要对应的改变

          @Override
            public Object getItem(int position) {
                if (position == 0 || position == mUserInfos.size() + 1) {
                    return null;
                }
                if (position < mUserInfos.size() + 1) {
                    return mInfos.get(position - 1);
                } else {
                    return mInfos.get(position - 2);
                }
            }

- 我们要实现的效果中ListView有两个分隔条目, 区分用户应用和系统应用, 有多个条目类型的ListView. 要实现这种效果, 需要重写 Adapter 里的两个方法:

            // 返回有多少种条目类型
            @Override
            public int getViewTypeCount() {
                return 2;
            }
            // 每个位置对应的条目的类型, 返回值表示条目类型
            @Override
            public int getItemViewType(int position) {
                // 注意返回值必须从0开始, 比如有三种类型, 就得返回 0, 1, 2.
                if (position == 0 || position == mUserDatas.size() + 1) {
                    return 0;
                } else {
                    return 1;
                }
            }

 

-getView方法中, 也得根据当前位置的条目类型返回相应的View:

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            int itemViewType = getItemViewType(position); // 先获取条目类型
            switch (itemViewType) { // 根据条目类型返回不同的View
                case 0:
                       convertView = new TextView(getApplicationContext());
                    tv = (TextView)convertView;
                    tv.setTextColor(Color.BLACK);
                    tv.setTextSize(14);
                    tv.setPadding(4, 4, 4, 4);
                    tv.setBackgroundColor(Color.parseColor("#ffcccccc"));
                    if(position == 0) {
                        tv.setText("用户程序( " + mUserDatas.size() + " 个)");
                    }else if(position ==  mUserDatas.size() + 1) {
                        tv.setText("系统程序( " + mSystemDatas.size() + " 个)");
                    }
                    break;
                case 1:
                    ViewHolder holder;
                    if (convertView == null || convertView instanceof TextView) {
                        convertView = View.inflate(getApplicationContext(), R.layout.item_appinfo, null);
                        holder = new ViewHolder();
                        holder.ivIcon = (ImageView) convertView.findViewById(R.id.item_appinfo_iv_icon);
                        holder.tvName = (TextView) convertView.findViewById(R.id.item_appinfo_tv_name);
                        holder.tvInstallPath = (TextView) convertView.findViewById(R.id
                                .item_appinfo_tv_install);
                        holder.tvSize = (TextView) convertView.findViewById(R.id.item_appinfo_tv_size);
                        convertView.setTag(holder);
                    } else {
                        holder = (ViewHolder) convertView.getTag();
                    }
                    AppInfo info = (AppInfo) getItem(position);
                    holder.tvName.setText(info.mName);
                    holder.ivIcon.setImageDrawable(info.mIcon);
                    holder.tvInstallPath.setText(info.mIsInstallSD ? "SD卡安装" : "手机内存");
                    holder.tvSize.setText(Formatter.formatFileSize(getApplicationContext(), info.mSize));
                    break;
            }
            return convertView;
        }

##分隔条目的显示和隐藏##

在布局文件里加一个 TextView, 样式和分隔条目TextView一模一样, 当滚动ListView的时候, 根据第一个可见条目,
做相应的显示即可.


       <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
    
            <ListView
                android:id="@+id/lv_am"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fastScrollEnabled="true" />
    
            <include
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                layout="@layout/public_loading" />
    
            <TextView
                android:id="@+id/tv_am_apphead"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#aaaaaa"
                android:padding="4px"
                android:textColor="@android:color/black"
                android:textSize="15sp"
                android:visibility="invisible" />
        </RelativeLayout>

 


-给ListView设置滚动监听: 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点


       // 设置listview滑动监听 在设置的时候 默认的方法都会都一遍
        lvAm.setOnScrollListener(new OnScrollListener() {
            // 滑动状态的改变
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {

            }

            // 滑动时不停的执行
            // 参1 当前listview 参2 可以见的第一个条目的索引 参3 可见的条目数量 参4 总数量
            @Override
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {
                if (mUserInfos == null || mSysInfos == null) {
                    // 防止空指针 在设置的时候 默认的方法都会都一遍
                    return;
                }
                if (firstVisibleItem >= mUserInfos.size() + 1) {
                    // 显示系统程序 更换固定条目内容
                    tvTopSize.setText("系统程序(" + mSysInfos.size() + "个)");
                } else {
                    tvTopSize.setText("用户程序(" + mUserInfos.size() + "个)");
                }
            }
        });

 

- 默认应该是隐藏的, 当加载数据完成之后再显示:


        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // 显示ListView头部信息
                mTvHeader.setVisibility(View.VISIBLE);
                // 隐藏进度条
                mLlLoading.setVisibility(View.GONE);
                // 给ListView设置Adapter
                mLvApp.setAdapter(new AppAdapter());
            }
        });


- ListView的属性:android:fastScrollEnabled="true",
    这个属性值设置为true的话, ListView会出现快速滑动条, 默认false

##PopupWindow的基本使用##   重点 重点 重点 重点 重点 重点 重点 重点

- 它有些方法和View有点像, 有些又和Dialog比较像.
    它也是通过Window加到屏幕上的, 但是它和Dialog的又不太一样, 它的弹出位置不固定.
- 基本用法, 创建一个示例项目.


        public void popup(View v) {
            TextView contentView = new TextView(getApplicationContext());
            contentView.setTextColor(Color.RED);
            contentView.setText("我是一个弹出窗口");
            int width = ViewGroup.LayoutParams.WRAP_CONTENT;
            int height = ViewGroup.LayoutParams.WRAP_CONTENT;
            // 弹出窗口, 第一个参数表示里面要显示的View, 后两个表示宽高.
            PopupWindow popupWindow = new PopupWindow(contentView, width, height);
            // 如果想让一个弹出窗能够在点击别的区域时或者按返回键时消失, 需要调用下面两个方法.
            // 表示可以获取焦点
            popupWindow.setFocusable(true);
            // 必须设置背景, 如果实在不想要, 可以设置 new ColorDrawable(Color.TRANSPARENT)
            popupWindow.setBackgroundDrawable(new ColorDrawable(Color.GREEN));
            // 显示在某个View的左下角
            // popupWindow.showAsDropDown(mTv);
            // 显示在屏幕的某个地方, 第一个参数只需要传当前Activity里任何一个View就行.
            // popupWindow.showAtLocation(mTv, Gravity.CENTER, 0, 0);
            // 显示在某个View的左下角, 并且指定x, y轴的偏移量
            popupWindow.showAsDropDown(mTv, mTv.getWidth(), -mTv.getHeight());
        }

##在手机卫士使用PopupWindow##

- 给ListView设置 OnItemClickListener, 点击某个条目后, 在当前条目下面弹出PopupWindow.
这里可以写一个单独的方法, 把当前条目对应的View作为参数传过去. 并且记录当前点击的对象.

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            // 只有点在应用条目上, 才应该显示PopupWindow, 点在分隔标题上不显示
            // parent 只的就是ListView, getItemAtPosition内部调用的是 Adapter的 getItem
            AppInfo appInfo = (AppInfo) parent.getItemAtPosition(position);
            if (appInfo != null) {
                mCurrentAppInfo = appInfo;
                showPopupWindow(view);
            }
        }
    
               /**
             * 显示listview单条点击的弹出框
             *
             * @param view
             */
            private void showPop(View view) {
                // 为空的时候再创建对象
                if (pop == null) {
                    // 设置宽高为包裹内容
                    int width = LayoutParams.WRAP_CONTENT;
                    int height = LayoutParams.WRAP_CONTENT;
                    View contentView = View.inflate(getApplicationContext(), R.layout.popup_am,
                            null);
                    contentView.findViewById(R.id.tv_popam_uninstall).setOnClickListener(this);
                    contentView.findViewById(R.id.tv_popam_open).setOnClickListener(this);
                    contentView.findViewById(R.id.tv_popam_share).setOnClickListener(this);
                    contentView.findViewById(R.id.tv_popam_info).setOnClickListener(this);
        
                    pop = new PopupWindow(contentView, width, height);
                    // 可以获取焦点
                    pop.setFocusable(true);
                    // 设置背景 只有设置了背景,点击 外部区域或者返回键 才会消失
                    pop.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));// 设置背景透明
                    // anchor 锚点 抛锚 让PopupWindow显示在指定的anchor下方
                    // pop.showAsDropDown(view);
        
                    // 设置动画弹出方式
                    pop.setAnimationStyle(R.style.PopAnimation);
                }
                pop.showAsDropDown(view, 60, -view.getHeight());
                // 参1 传入activity里任意一个view就可以
                // pop.showAtLocation(mtv, Gravity.LEFT | Gravity.CENTER_VERTICAL, 50,
                // 0);
            }

- 动画样式  pop.setAnimationStyle(R.style.PopAnimation);
这里可以仿照输入法的动画样式, 自己写一个, 在 styles.xml中:

        <style name="PopAnimation">
            <item name="android:windowEnterAnimation">@anim/pop_am_enter</item>
            <item name="android:windowExitAnimation">@anim/pop_am_exit</item>
        </style>

        <?xml version="1.0" encoding="utf-8"?>
            <set xmlns:android="http://schemas.android.com/apk/res/android"
                android:shareInterpolator="false" >
            
                <translate
                    android:duration="@android:integer/config_shortAnimTime"
                    android:fromXDelta="100%"
                    android:interpolator="@interpolator/overshoot"
                    android:toXDelta="0" />
            
                <alpha
                    android:duration="@android:integer/config_shortAnimTime"
                    android:fromAlpha="0.5"
                    android:interpolator="@interpolator/decelerate_cubic"
                    android:toAlpha="1.0" />
            
            </set>


            <?xml version="1.0" encoding="utf-8"?>
                <set xmlns:android="http://schemas.android.com/apk/res/android"
                    android:shareInterpolator="false" >
                
                    <translate
                        android:duration="@android:integer/config_shortAnimTime"
                        android:fromXDelta="0"
                        android:interpolator="@interpolator/anticipate"
                        android:toXDelta="100%" />
                
                    <alpha
                        android:duration="@android:integer/config_shortAnimTime"
                        android:fromAlpha="1.0"
                        android:interpolator="@interpolator/accelerate_cubic"
                        android:toAlpha="0.0" />
                
                </set>

- 注意使用了两个interpolator文件:

        <overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android"/>
        <anticipateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"/>

# 卸载, 打开, 分享, 信息#
- 点击PopupWindow里面的文字时, 实现相应的功能

        /**
         * 显示app信息页面
         *
         * @param packageName
         */
        private void showInfo(String packageName) {
            // <action android:name="android.settings.APPLICATION_DETAILS_SETTINGS"
            // />
            // <category android:name="android.intent.category.DEFAULT" />
            // <data android:scheme="package" />
            Intent intent = new Intent();
            intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.setData(Uri.parse("package:" + packageName));
            startActivity(intent);
        }
    
        /**
         * 分享app
         */
        private void shareApp() {
            // <action android:name="android.intent.action.SEND" />
            // <category android:name="android.intent.category.DEFAULT" />
            // <data android:mimeType="text/plain" />
    
            // sharesdk mob 友盟 极光 百度云推送
    
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_SEND);
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.setType("text/plain");
            intent.putExtra(Intent.EXTRA_TEXT, "分享一个app,,:" + mCurrentInfo.name);
            startActivity(intent);
        }
    
        /**
         * 打开一个app
         *
         * @param packageName
         */
        private void openApp(String packageName) {
            PackageManager pm = getPackageManager();
            // 返回启动页面的intent
            Intent openIntent = pm.getLaunchIntentForPackage(packageName);
            // 有些应用没有页面 只有后台程序 需要判断空
            if (openIntent != null) {
                startActivity(openIntent);
            }
        }
    
        /**
         * 卸载app
         *
         * @param packageName
         */
        private void unInstallApp(String packageName) {
            // <action android:name="android.intent.action.VIEW" />
            // <action android:name="android.intent.action.DELETE" />
            // <category android:name="android.intent.category.DEFAULT" />
            // <data android:scheme="package" />
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_DELETE);
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.setData(Uri.parse("package:" + packageName));
            startActivityForResult(intent, REQUESTCODE_UNINSTALL);
        }
    
        

## 卸载后的处理 ##
- 简单的做法, 点击卸载时, startActivityForResult, 从卸载界面回来的时候, 重新获取一遍数据即可.  
    注意获取数据之前, 先清空之前的数据集合.

       @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == REQUESTCODE_UNINSTALL) {// 从卸载页面返回
                // Activity.RESULT_OK
                // resultCode
                // System.out.println("resultCode ==" + resultCode);
    
                // 从新获取数据
                // 获取数据前先清空
                    userAppInfos.clear();
                    sysAppInfos.clear();
                    appInfos = AppInfoProvider.getAllAppInfo(getApplicationContext());
                    // 把用户程序和系统程序分开
                    for (AppInfo info : appInfos) {
                        if (info.isSystem) {
                            sysAppInfos.add(info);
                        } else {
                            userAppInfos.add(info);
                        }
                    }
                    appAdapter.notifyDataSetChanged();
            }
        }


- 比较高级的做法, 监听系统卸载应用的广播:  
  在 onCreate 中注册广播接收者:

        // 注册应用卸载的广播
        IntentFilter filter = new IntentFilter();
        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addDataScheme("package");
        registerReceiver(mPackageReceiver, filter);

- 在 onDestroy 中解除注册:

        unregisterReceiver(mPackageReceiver);

 对应的广播接收者:


        private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                System.out.println("接收到卸载广播");
                String dataString = intent.getDataString();
                System.out.println("卸载了:" + dataString);
                String packageName = dataString.replace("package:", "");
                // 只需要遍历用户集合就可以了, 因为系统的删不掉
                // 一边遍历一遍移除需要使用 iterator
                ListIterator<AppInfo> iterator = mUserDatas.listIterator();
                while (iterator.hasNext()) {
                    AppInfo next = iterator.next();
                    if (next.packageName.equals(packageName)) {
                        // 移除
                        iterator.remove();
                        mUserInfos.remove(info);
                        break;
                    }
                }
                // 刷新ListView
                mAdapter.notifyDataSetChanged();
            }
        };


    但其实使用广播是不靠谱的, 因为有的系统不发这个广播...

- bug
    
    点击分割条目的bug   单条点击事件

    if (position == 0 || position == mUserInfos.size() + 1) {
        //如果点击的是分割条目 不处理
        return;
    }

    把固定条目变成可点击 处理透过点击的bug
    
     android:clickable="true"

转载于:https://my.oschina.net/u/2884845/blog/784006

### SVG `<symbol>` 标签的使用方法及案例 SVG 的 `<symbol>` 栃签是用于定义可复用的图形模板对象。这些图形对象不会直接显示在画布上,而是通过 `<use>` 标签引用,从而实现高效的图标管理和复用。这种方式特别适合在 Web 开发中使用 SVG 图标系统。 #### 基本用法 在 SVG 中,可以将多个图标定义在 `<symbol>` 元素中,并为每个 `<symbol>` 指定一个唯一的 `id` 属性。之后,通过 `<use>` 标签并使用 `href`(或旧版本的 `xlink:href`)属性指向该 `id`,即可复用对应的图形。 ```xml <svg style="display: none;"> <symbol id="icon-home" viewBox="0 0 24 24"> <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" /> </symbol> <symbol id="icon-settings" viewBox="0 0 24 24"> <path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.31-2.13A.49.49 0 0 0 13 4h-3c-.25 0-.46.18-.49.42l-.31 2.13c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.21 8.26c-.11.2-.06.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.31 2.13c.03.24.24.42.49.42h3c.25 0 .46-.18.49-.42l.31-2.13c.59-.24 1.13-.57 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.2.07-.47-.12-.61l-2.01-1.58zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z" /> </symbol> </svg> ``` #### 使用 `<use>` 引用图标 通过 `<use>` 标签可以引用之前定义的 `<symbol>` 图标。以下是一个使用 `<use>` 显示图标的示例: ```xml <svg class="icon"> <use href="#icon-home" /> </svg> <svg class="icon"> <use href="#icon-settings" /> </svg> ``` 每个 `<use>` 标签都通过 `href` 属性引用了 `<symbol>` 的 `id`,从而复用了对应的图形。 #### JavaScript 动态创建 SVG 图标 如果需要通过 JavaScript 动态创建 SVG 图标,需要使用 `createElementNS` 来创建 SVG 元素,并正确设置命名空间。以下是动态创建 SVG `<use>` 图标的方法: ```javascript let icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); icon.setAttribute("class", "icon"); icon.setAttribute("aria-hidden", "true"); icon.setAttribute("fill", "currentColor"); let use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttribute("href", "#icon-home"); // 替换为实际的 icon id icon.appendChild(use); document.body.appendChild(icon); ``` 上述代码动态创建了一个 SVG 元素,并通过 `<use>` 引用了之前定义的图标。这种方式适合在前端动态加载和切换图标。 #### 优势与适用场景 使用 `<symbol>` 和 `<use>` 的方式具有以下优势: - **减少 HTTP 请求**:所有图标整合在同一个 SVG 文件中,减少了多个请求。 - **可缓存**:SVG 文件可以被浏览器缓存,提高性能。 - **易于管理**:图标集中管理,方便维护和更新。 - **可复用性**:通过 `<use>` 可以多次复用同一个图标,避免重复定义。 这种方法非常适合构建图标系统,特别是在大型 Web 项目中[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值