android6.0,android7.0,android 8.0适配以及关于recycleView那些事儿

前言 注册简书并着手写文章是去年年底开始的,今年陆续写了几篇文章,排版格式有所改善,于是想把之前排版拿不出手的的文章,拾掇拾掇,重新写写,也算是对以前知识的简单回顾,归纳总结。
正文
重点记录这一年开发中的主要收获和开发中一些比较细的注意事项。

1:Android6.0 权限适配问题

目前开发的这个项目中,动态获取App需要的权限是纯手写的,没用到第三方。
基本需求是这样:
1.0 app闪屏页面将App所需权限,全部获取到,否则App不能进入;
2.0 单个权限系统弹窗提示请求权限,用户给与,继续获取下一个权限;
3.0 单个权限系统弹窗提示请求权限,用户拒绝,弹窗提示获取权限的必要性,点击确定继续获取权限,点击取消退出App;
4.0 单个权限系统弹窗提示请求权限,点击确定,再次请求权限,用户勾选不再提示并拒绝,弹窗提示权限必要性,提示去设置中开启,点击确定,进入设置,取消退出;
5.0 单个权限系统弹窗提示请求权限,再次请求权限,用户未勾选不再提示,但是拒绝,重复3.0;
代码逻辑
1:所需权限使用集合或者数组承载,变量index记录当前请求权限下标位置;代码:

static final String[] permissions = new String[]{
            Manifest.permission.READ_PHONE_STATE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_CONTACTS,

            Manifest.permission.CAMERA,
            Manifest.permission.READ_SMS,
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.READ_CALL_LOG

    };
private int index=0;

2.0 检查当前index位置的权限是否授予,是,index+1检查下一个,否,获取当前权限;代码:

   /**
     * 检查单个权限是否获取,每个权限单独处理
     * 思路:index代表权限组的坐标,初始化为0;
     *  检查index坐标位置的权限是否获取
     *  获取到:则index++(index<8)获取下一个权限;
     *  未获取到 ,则获取权限(用户给这个权限则index++;获取下一个权限;用户不给这个权限,弹窗提示获取权限的必要性);
     *
     */
    private void checkSinglePermission(){
        if(ContextCompat.checkSelfPermission(this,
                permissions[index])
                != PackageManager.PERMISSION_GRANTED){
            getSinglePermission(index);

        }else{
            getNextPermission();
        }
    }
    private void getSinglePermission(int index){
        Logger.d("权限下标"+index);
        ActivityCompat.requestPermissions(this,
                new String[]{permissions[index]},
                index);

    }
    /**
     *  获取下一个权限
     *
     */

    public void getNextPermission() {
        if(index<permissions.length-1){
            index=index+1;
            checkSinglePermission();
        }else{
            allPermissionsGeted();
        }
    }

3.0 获取单个权限,回调处理
用户授予,继续稽查下一个;
用户拒绝,加入被拒绝过的权限集合中并弹窗提示,弹窗确定重新获取,取消退出;代码:

    /**
     * 用户给予当前权限继续获取下一个权限,没有获取权限处理拒绝的情况
     * @param requestCode
     * @param pers
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] pers, @NonNull int[] grantResults) {
        if(grantResults[0] == PackageManager.PERMISSION_GRANTED){//拿到权限
            getNextPermission();
        }else{//权限被拒绝
            handleUserRefusePermissons(pers);
        }
        super.onRequestPermissionsResult(requestCode, pers, grantResults);
    }

    /**
     * 如果拒绝权限集合中n不包含该权限说明这是第一次拒绝 ,弹窗提示
     * 如果拒绝权限集合中包含该权限说明之前拒绝过一次(这种情况下用户可以选择勾选拒绝不在提示)
     *          判断用户是否勾选不在提示
     *             勾选:针对单个权限进行文字说明,提示去设置中修改权限
     *             未勾选:继续弹窗提示
     * @param pers
     */
    private void handleUserRefusePermissons(String[] pers) {
        if(!refusedPermission.contains(pers[0])){
            showMissingPermissionDialogHint();
            refusedPermission.add(pers[0]);
        }
    }
    /**
     * 弹窗说明权限必要性,要么退出,要么继续获取该权限
     */
    private void showMissingPermissionDialogHint() {
        String msg=switchStatus(false,permissions[index]);

        new CustomDialog.Builder(SplashActivity.this)
                .setTitle("提示")
                .setMessage(msg)
                .setCancelable(true)
                .setNegativeButton("退出",
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog,
                                                int arg1) {
                                dialog.dismiss();
                                finish();
                            }
                        })

                .setPositiveButton("好的",
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog,
                                                int arg1) {
                                dialog.dismiss();
                                checkSinglePermission();
                            }

                        }).create().show();
    }

3.0 说明必要性之后用户同意继续获取,就重新请求权限。如果用户再次拒绝(真调皮,估计是测试干的事),这种情况属于二次拒绝,要考虑用户是否勾选不再提示,分情况进行处理。
勾选,弹窗提示去设置中开启,否则退出;
未勾,选继续弹窗提示说明权限必要性;
代码:

 /**
     * 如果拒绝权限集合中n不包含该权限说明这是第一次拒绝 ,弹窗提示
     * 如果拒绝权限集合中包含该权限说明之前拒绝过一次(这种情况下用户可以选择勾选拒绝不在提示)
     *          判断用户是否勾选不在提示
     *             勾选:针对单个权限进行文字说明,提示去设置中修改权限
     *             未勾选:继续弹窗提示
     * @param pers
     */
    private void handleUserRefusePermissons(String[] pers) {
        if(!refusedPermission.contains(pers[0])){
            showMissingPermissionDialogHint();
            refusedPermission.add(pers[0]);
        }else{//如果包含说明之前至少拒绝过一次,系统会提示"不再提醒"的选择
            boolean isNoSeclectd=
                    ActivityCompat.shouldShowRequestPermissionRationale(SplashActivity.this,pers[0]);
            /**
             * 如果之前拒绝过一次  并且isNoSeclectd=false  就说明用户勾选了不再提醒
             * isNoSeclectd=ture  就说明用户没有勾选 不再提醒
             */

            if(isNoSeclectd){
                showMissingPermissionDialogHint();
            }else{
                String msg=switchStatus(true,pers[0]);
                showMissingPermissionDialog(msg);
            }
        }
    }
    public void showMissingPermissionDialog(String msg) {

        new CustomDialog.Builder(BaseActivity.this)
                .setTitle("提示")
                .setMessage(msg)
                .setCancelable(false)
                .setNegativeButton("退出",
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog,
                                                int arg1) {
                                dialog.dismiss();
                                ActivityCacheCollectUtil.finishAllActivity();
                            }
                        })
                .setPositiveButton("去开启",
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog,
                                                int arg1) {
                                dialog.dismiss();
                                startAppSettings();
                            }

                        }).create().show();
    }
    /**
     * 启动用户设置
     */

    private void startAppSettings() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.parse(PACKAGE_URL_SCHEME + getPackageName()));
        startActivity(intent);
    }

到这里,关于动态获取权限,基本上打通了,总结一下基本上就以下几点:

  • 检查权限是否获取;
 if(ContextCompat.checkSelfPermission(this,
                permissions[index])
                != PackageManager.PERMISSION_GRANTED){}
  • 请求获取单个或者多个权限;
ActivityCompat.requestPermissions(this,
                new String[]{permissions[index]},
                index);//单个权限获取

ActivityCompat.requestPermissions(this,
                new String[]{permissions[index],permissions[index],permissions[index]},
                index);//多个权限获取
  • 检查用户是否勾选不再提示;
boolean isNoSeclectd=
                    ActivityCompat.shouldShowRequestPermissionRationale(SplashActivity.this,pers[0]);

其他的都是逻辑问题了,当然真正应用但项目中还要结合activity的生命周期考虑跳转设置回来后的处理逻辑,这里就不赘述了。

2:Android7.0 针对sdCard读写权限适配

随着Android 本版号越来越高,Android对隐私的保护力度也越来越大。从Android6.0引入的动态权限控制(Runtime Permissions)到Android7.0的“私有文件夹被限制访问。“StrictMode API 政策”。这些更改在为用户带来更加安全的操作系统的同一时候也为开发人员带来了一些新的任务。

  • 私有文件的文件权限不在放权给全部的应用,使用 MODE_WORLD_READABLE 或 MODE_WORLD_WRITEABLE 进行的操作将触发 SecurityException。
应对策略:这项权限的变更将意味着你无法通过File API訪问手机存储上的数据了。
基于File API的一些文件浏览器等也将受到非常大的影响,看到这大家是不是惊呆了呢,
只是迄今为止,这样的限制尚不能全然运行。

应用仍可能使用原生 API 或 File API 来改动它们的私有文件夹权限。 可是,Android官方强烈反对放宽私有文件夹的权限。

能够看出收起对私有文件的訪问权限是Android将来发展的趋势。
  • 给其它应用传递 file:// URI 类型的Uri,可能会导致接受者无法訪问该路径。 因此,在Android7.0中尝试传递 file:// URI 会触发 FileUriExposedException。
应对策略:大家能够通过使用FileProvider来解决这一问题。
  • DownloadManager 不再按文件名称分享私人存储的文件。
    COLUMN_LOCAL_FILENAME在Android7.0中被标记为deprecated, 旧版应用在訪问 COLUMN_LOCAL_FILENAME时可能出现无法訪问的路径。

面向 Android N 或更高版本号的应用在尝试訪问 COLUMN_LOCAL_FILENAME 时会触发 SecurityException。

应对策略:大家能够通过ContentResolver.openFileDescriptor()来訪问由 DownloadManager 公开的文件。
  • 应用间共享文件
    在Android7.0系统上。Android 框架强制运行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。
    假设一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用系统相机拍照,或裁切照片。
应对策略:若要在应用间共享文件。能够发送 content:// URI类型的Uri,并授予 URI 暂时訪问权限。 进行此授权的最简单方式是使用 FileProvider类。 如需有关权限和共享文件的很多其它信息,请參阅共享文件。
 给其它应用传递 file:// URI 类型的Uri。可能会导致接受者无法訪问该路径。
因此。在Android7.0中尝试传递 file:// URI 会触发 FileUriExposedException

看到这里,估计有人会说,你说的啥啊,看不懂,说实话,我也不太懂,我也是从大神文章cv过来的。我开发中遇到7.0的适配地方有两个地方1:拍照存储;2:版本更新本地存储APK,总结起来就是需要对SdCard进行读写操作的时候,就需要适配(哇,好接地气,秒懂),适配代码如下:
1.0 在manifest清单文件里注冊provider

<!--这个地方的name中FileProvider和aothorities中的FileProvider必须保持一致
//${applicationId}代表项目报名路径,换成你自己的项目包名也行,具体为啥,我也不知道
//exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 暂时訪问权限。
-->
   
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_provider" />
        </provider>

2.0 指定共享的文件夹
为了指定共享的文件夹我们须要在资源(res)文件夹下创建一个xml文件夹,然后创建一个名为“file_provider”(android:resource="@xml/file_provider"一致)(名字能够随便起,仅仅要和在manifest注冊的provider所引用的resource保持一致就可以)的资源文件。内容例如以下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!--&lt;!&ndash;3、对应外部内存卡根目录:Environment.getExternalStorageDirectory()&ndash;&gt;-->
    <external-path name="card" path=""/>
    <!--<files-path name="name" path="path" /> //相当 Context.getFilesDir() + path, name是分享url的一部分-->
    <!--<cache-path name="name" path="path" /> //getCacheDir()-->
    <!--<external-path name="name" path="path" /> //Environment.getExternalStorageDirectory()-->
    <!--<external-files-path name="name" path="path" />//getExternalFilesDir(String) Context.getExternalFilesDir(null)-->
    <!--<external-cache-path name="name" path="path" /> //Context.getExternalCacheDir()-->
</paths>

备注:上述代码中path=”“,是有特殊意义的,它代表根文件夹。也就是说你能够向其它的应用共享根文件夹及其子文件夹下不论什么一个文件了,假设你将path设为path=”pictures”,
那么它代表着根文件夹下的pictures文件夹(eg:/storage/emulated/0/pictures),假设你向其它应用分享pictures文件夹范围之外的文件是不行的。
3.0 使用FileProvider:上述准备工作做完之后,如今我们就能够使用FileProvider了(代码安装apk,安装程序)。

Intent intent = new Intent(Intent.ACTION_VIEW);
 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//单独处理安卓7.0读取内存卡权限问题
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri apkUri = FileProvider.getUriForFile(SplashActivity.this,  getPackageName()+".FileProvider", apkFile);
//getPackageName()+".FileProvider"获取包名,和mainfest中相同!!!!!!
            intent.addFlags(
                    Intent.FLAG_GRANT_READ_URI_PERMISSION |
                            Intent.FLAG_GRANT_WRITE_URI_PERMISSION);//加入这一句表示对目标应用暂时授权该Uri所代表的文件
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
        }
Logger.d("安装路径"+apkFile.toString());
        startActivity(intent);
        Process.killProcess(Process.myPid());

参考文章android在线更新适配Android6.0-8.0

补充(Android 8.0适配问题)

  /**
     * 安装逻辑如下(前提针对Android 6.0 必要权限,全部已经获取到):
     * 1:判断机型是否大于Android 8.0;
     *    1.1 小于Android 8.0 的话
     *        询问是否大于Android7.0
     *          1.1.1 小于Android 7.0
     *              正常安装;
     *          1.1.2 大于Android 7.0
     *              针对Android7.0使用FilePrivder等逻辑进行安装处理
     *    1.2 大于Android 8.0 的话
     *          1.2.1:AndroidManifast.xml中静态获取 
     *              <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
     *          1.2.2:java代码中检查系统是否授予安装未知来源权限;
     *              1.2.2.1: 系统已经授予该权限,直接安装1.1.2(大于Android7.0) 进行安装
     *              1.2.2.2: 系统未授予该权限,代码执行进行询问跳转授予该权限页面
     */

    private void install() {
        boolean installAllowed;
        //如果是Android 8.0 的机型就询问是否允许安装未知来源的相关权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //检查是否有安装未知来源的权限
            installAllowed = this.getPackageManager().canRequestPackageInstalls();
            Logger.d("是否有Android 8.0"+installAllowed);
            if (installAllowed) {
                //如果有,直接安装
                installAPK();
            } else {
                //如果没有,进行询问
                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + this.getPackageName()));
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
                return;
            }
        } else {
            installAPK();
        }
    }

    /**
     * 强制更新说明两点:
     * 1:强制更新涉及到读写sdCard权限考虑到适配到andorid 7.0+
     * 解析包出现异常
     * http://blog.csdn.net/y505772146/article/details/55255344
     * 安装操作
     */

    private void installAPK() {
        File apkFile =
                new File(apkDownPath, apkDownFileName);
        if(!apkFile.exists()){
            ToastUtils.makeLongText(this,"安装包路径找不到,请退出程序后重新进入,重新安装");
            return;
        }
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);
        //单独处理安卓7.0读取内存卡权限问题
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri apkUri = FileProvider.getUriForFile(SplashActivity.this, "com.tudicaifu.agriculture.FileProvider", apkFile);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//加入这一句表示对目标应用暂时授权该Uri所代表的文件
        } else {
            intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
        }
        Logger.d("安装路径"+apkFile.toString());
        startActivity(intent);
        //部分手机会先执行这里,导致出现异常,暂时不建议杀掉进程;
//        Process.killProcess(Process.myPid());
        spUtils.put(SpUtils.IS_LOGIN,false);
    }

备注:我在Android 8.0适配部分去掉了下面这一行代码:

Process.killProcess(Process.myPid());

去掉的主要原因是:在部分华为Android8.0机型上,会出现安装包解析异常,而且出现这种异常是偶然性的,不知道的什么时候会出现一次。
参考文章1参考文章2

3:recycleView相关

  3.1 recyclewView的Item点击监听

    1.0 创建接口回传监听数据

public interface ItemClicker {
    void myClick(View view, int type);
    void myViewPosition(int position, int type);
    void myViewPosition(View view, int position, int type);
}

    2.0 adapter中onClick监听,回传数据

public class ListSignsAdapter extends RecyclerView.Adapter<ListSignsAdapter.MyViewHolder> {

    private Context context;
    private List<HomeSignsBean> list;
    private ItemClicker mItemClicker;


    public ListSignsAdapter(Context context, List<HomeSignsBean> list, ItemClicker mItemClicker) {
        this.context = context;
        this.list = list;
        this.mItemClicker = mItemClicker;
    }
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_list_sign, parent, false);
        MyViewHolder myViewHolder = new MyViewHolder(view);
        return myViewHolder;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.llParent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mItemClicker.myClick(v, tempPosition);
            }
        });
    }
    @Override
    public int getItemCount() {
        return list.size();
    }
    class MyViewHolder extends RecyclerView.ViewHolder {

        RelativeLayout llParent;
      
        public MyViewHolder(View itemView) {
            super(itemView);
            llParent = (RelativeLayout) itemView.findViewById(R.id.rlParent);  
        }
    }

}

    3.0 Activity中实现接口,接收数据

public class AssureListActivity extends BaseActivity implements ItemClicker{
    @Override
    public void myClick(View view, int type) {
//处理普通的item点击监听
    }
    @Override
    public void myViewPosition(int position, int type) {
//处理普通的item点击监听
    }
    @Override
    public void myViewPosition(View view, int position, int type) {
//处理item中单独控件的点击事件
    }
   
}

  3.2 recyclewView的分割线

recycleView官方并没有提供设置divider的Api,我比较喜欢将分割线写在item布局中,并且再adapter中判断是不是最后一个item,如果是将item布局中的分割线控件隐藏,如果不是最后一个,不需要隐藏。

  3.3 recyclewView和ScrollView的嵌套问题

    3.3.1 滑动冲突
    3.3.2 数量显示不全的问题

    上面两个问题,网上解决办法有很多,我开发中用的比较基础(据说是比较综合)的方法,示例代码查看注释如下:

    布局部分
<com.tudicaifu.agriculture.view.MyScrollview
        style="@style/scrollview"
        android:id="@+id/scrollView"
//有这个属性才能真正充满屏幕
        android:fillViewport="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
//recycleView外面嵌套一层父布局,解决数量显示不全的问题
            <RelativeLayout
                android:layout_width="wrap_content"
//属性必须设置
                android:descendantFocusability="blocksDescendants"
                android:layout_height="wrap_content">
                <android.support.v7.widget.RecyclerView

                    android:id="@+id/recyclerView"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@color/gray_f8"
                    android:overScrollMode="never"
                    android:layoutAnimation="@anim/anim_listview_item"
                    app:refreshHeaderLayout="@layout/layout_commonrecyclerview_refresh_header" />
            </RelativeLayout>
        </LinearLayout>
    </com.tudicaifu.agriculture.view.MyScrollview>
public class MyScrollview extends ScrollView{
    private int downX;
    private int downY;
    private int mTouchSlop;

    public MyScrollview(Context context) {
        super(context);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public MyScrollview(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public MyScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        int action = e.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) e.getRawX();
                downY = (int) e.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int moveY = (int) e.getRawY();
                if (Math.abs(moveY - downY) > mTouchSlop) {
                    return true;
                }
        }
        return super.onInterceptTouchEvent(e);
    }
}

    滑动冲突和数量显示不全的问题,得以解决,更多办法请参考
    文章1
    推荐文章2

4:Android动画

这个板块说实话,我自己现在都不敢说掌握了,每次用的时候,都需要参考大神文章,所以我就不赘述了,将自己认为不错的文章分享给大家。
补间动画写的比较全面
知乎上关于android动画的问题,里面有很多源码解读;
关于android属性动画的介绍,写的非常基础全面;
android关于属性动画的优化处理部分

5:popWindow

开发中经常用到自定义PopWindow底部弹窗,弹出备选项供用户选择,可以有以下两种情况:

1. 备选数量固定,使用多个textView组成ListView的样子,这样写出现的问题:
  • 部分机型,点击textView不能响应点击事件(小米和vivo手机)
    解决办法:见示例代码(不能根本解决,部分手机偶尔还是会出现)
  • 布局错乱的问题
    解决办法:每次显示pop,都重新创建pop对象(暂时没有想到更好的办法)
2. 备选数量不固定,使用ListView(RecycleView)展示备选项,这样写出现的难点(上面提到的问题没有遇到):
  • 备选项弹窗最高高度固定,备选项数量不足的时候,包裹内容,备选数量充足,弹窗高度不能高于设定的最高高度。
    解决办法:见示例代码布局部分

示例代码

示例代码结构说明:
1. 布局部分整体是LinearLayout,整体分为两部分1:上半部分空白占用区即TextView(id=@+id/view_click),背景色和父布局背景色相同并且高度固定,作用限制下半部分最大高度;2:下半部分,显示备选项,设置layout_gravity="bottom",使其靠近底部,高度根据listView高度决定,但是不会超过限制的最大高度,解决了一个难点。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/pop_layout"
    android:gravity="bottom"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
              android:background="@color/pop_bg">
       <TextView
    android:id="@+id/view_click"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="350dp"
    />
    <LinearLayout
        android:layout_gravity="bottom"
        android:id="@+id/ll_content"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/pop_bg">
            <TextView
                android:id="@+id/mTv_title"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@color/white"
                android:gravity="center"
                android:paddingLeft="80dp"
                android:text="你好"
                android:textColor="@color/gray3"
                android:textSize="@dimen/font_large17" />
            <TextView
                android:id="@+id/mTv_exit"
                android:layout_width="80dp"
                android:layout_height="match_parent"
                android:background="@color/white"
                android:gravity="center"
                android:paddingLeft="20dp"
                android:paddingRight="20dp"
                android:text="关闭"
                android:textColor="@color/gray6"
                android:textSize="@dimen/font_normal" />
        </LinearLayout>
        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="@color/dividercolor"/>
        <ListView
            android:overScrollMode="never"
            android:background="@color/white"
            android:layout_gravity="bottom"
            android:id="@+id/mLv_bandlists"
            android:scrollbars="none"
            android:divider="@color/dividercolor"
            android:dividerHeight="1dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"></ListView>
    </LinearLayout>
</LinearLayout>
2.java类中,关键代码解决以上问题
//设置弹出窗体的高度,使得窗口充满整个屏幕(如果不设置,部分手机会出现不能充满整个屏幕)
        this.setHeight(WindowManager.LayoutParams.MATCH_PARENT);
        //设置弹出窗体的可点击,解决textView点击无效果的问题
        this.setFocusable(true);
        this.setTouchable(true);
public class Popu_listview_select extends PopupWindow {
    private final TextView mTvTItle;
    private final View viewBg;
    private LinearLayout llContent;
    private  View view;

    private ListView mLvBands;
    private int click=-1;
    private MyAdapter adapter;
    private String title;
    private TextView mTvExit;

    @SuppressLint("inflateParams")
    public Popu_listview_select(Context context , View.OnClickListener itemsOnClick, AdapterView.OnItemClickListener click,List<String>lists,String title){
        super(context);
        LayoutInflater inflater= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view=inflater.inflate(R.layout.popu_listview_divider,null);
        //弹窗文本显示内容
        llContent = (LinearLayout) view.findViewById(R.id.ll_content);
        //背景控件
        viewBg=view.findViewById(R.id.view_click);
        mLvBands= (ListView) view.findViewById(R.id.mLv_bandlists);
        mTvTItle= (TextView) view.findViewById(R.id.mTv_title);
        mTvExit= (TextView) view.findViewById(R.id.mTv_exit);
        mTvTItle.setText(title);
        this.title=title;
        mTvExit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dissmissAnim();
            }
        });

        viewBg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(Popu_listview_select.this.isShowing()){

                    dissmissAnim();
                }
            }
        });
        mLvBands.setOnItemClickListener(click);
        //设置selectPicPopuWindow的view
        this.setContentView(view);
        //设置弹出窗体的宽度
        this.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
        //设置弹出窗体的高度
        this.setHeight(WindowManager.LayoutParams.MATCH_PARENT);
        //设置弹出窗体的可点击
        this.setFocusable(true);
        this.setTouchable(true);

        this.setBackgroundDrawable(new ColorDrawable(0));
        this.setOutsideTouchable(true);

        setData(context,lists,title);
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            @SuppressLint("ClickableViewAccessibility")
            public boolean onTouch(View v, MotionEvent event) {
                int height=view.findViewById(R.id.pop_layout).getTop();
                int y= (int) event.getY();
                if(event.getAction()==MotionEvent.ACTION_UP){
                    if(y<height){
                        dissmissAnim();


                    }
                }
                return true;
            }
        });
        comeInAnim();




    }
    private void comeInAnim(){
        //获取弹窗文本显示内容区域的测量高度,分别对背景控件(透明度变化,)文本显示(位置变化)做属性动画处理
        int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
        int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
        llContent.measure(w, h);
        ObjectAnimator anim=ObjectAnimator.ofFloat(viewBg,"alpha",0f,1f);
        ObjectAnimator anim1=ObjectAnimator.ofFloat(llContent,"translationY",llContent.getMeasuredHeight(),0);
        AnimatorSet set=new AnimatorSet();
        set.playTogether(anim,anim1);
        set.setDuration(AppConstant.ANIMATION_TIME);
        set.start();

    }
    public void  dissmissAnim(){
        ObjectAnimator anim=ObjectAnimator.ofFloat(viewBg,"alpha",1f,0f);
        ObjectAnimator anim1=ObjectAnimator.ofFloat(llContent,"translationY",0,llContent.getMeasuredHeight());
        AnimatorSet set=new AnimatorSet();
        set.playTogether(anim,anim1);
        set.setDuration(AppConstant.ANIMATION_TIME);
        set.start();
        set.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                dismiss();

            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
    }
    public void setClickItemNum(int i){
        click=i;
        adapter.notifyDataSetChanged();

    }

    @Override
    public void dismiss() {
        Logger.d("消失监听api");
        super.dismiss();


    }


    public void setData(Context context, List<String>lists, String title) {
        comeInAnim();
        mTvTItle.setText(title);
        if(adapter==null){
            adapter=new MyAdapter(lists,context);
            mLvBands.setAdapter(adapter);
        }else{
            adapter.notifyDataSetChanged();

        }

    }
    class MyAdapter extends BaseAdapter{
        private List<String>lists;
        private Context context;
        private LayoutInflater infllater;


        public MyAdapter(List<String> lists, Context context) {
            this.lists = lists;
            this.context = context;
            this.infllater=LayoutInflater.from(context);

        }


        @Override
        public int getCount() {
            return lists.size();
        }

        @Override
        public Object getItem(int i) {
            return lists.get(i);
        }

        @Override
        public long getItemId(int i) {
            return i;
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            ViewHolder holder;
            if(view==null){
                view=infllater.inflate(R.layout.pop_select_item,null);
                holder=new ViewHolder();
                holder.mTv= (TextView) view.findViewById(R.id.mTv_content);
                holder.mIv= (ImageView) view.findViewById(R.id.mIv);
                holder.mViewDian=view.findViewById(R.id.view_dian);
                view.setTag(holder);
            }else{
                holder= (ViewHolder) view.getTag();
            }
            holder.mTv.setTag(i);
            holder.mTv.setText(lists.get(i));
            if(click==i){
                holder.mViewDian.setVisibility(View.VISIBLE);
            }else{
                holder.mViewDian.setVisibility(View.GONE);
            }
            return view;
        }
        class ViewHolder {
            TextView mTv;
            ImageView mIv;
            View mViewDian;
        }
    }
}

小结:遇到类似的问题还是使用ListView或者RecycleView,不建议使用其他控件组装,即使数量固定。

6:图片加载和展示

Tip1:当显示图片尺寸明显大于控件ImageView尺寸的时候
关键问题设置imageView的scrapType();如果设置为fit相关的系列,图片就会压缩宽高比例,就是有明显压缩的感觉,建议设置center相关的,这个虽然只显示部分图片内容,但是没有直接被压缩的感觉,效果比较好;
Tip2:关于glide加载图片的使用优化
参考glide和picasso的对比,我更倾向于使用glide,glide无论从性能还是加载速度上都优于picasso.但是glide也有自己的不足,就是图片质量(glide默认使用RGB_565,picasso默认使用RGB_888)和本地缓存(glide缓存的是当前imageView大小的图片,同一张图片加载另一个不同大小的imageView的时候,需要重新网络获取,并将新尺寸的图片重新缓存一次,picasso默认缓存图片的原始尺寸)针对这里提到的不足,做出相应的优化,glide加载图片就会更加完美了(修改图片质量,会加载内存消耗,不建议全局使用参考这里),示例代码如下:

    //加载图片
    public static void loadImageIntoImageView(Context context, Object path, ImageView imageView) {
        RequestOptions options = new RequestOptions();
        options.diskCacheStrategy(DiskCacheStrategy.ALL);
        options.placeholder(R.mipmap.default_image);
        options.error(R.mipmap.default_image);
        DrawableTransitionOptions opt=new DrawableTransitionOptions();
        opt.crossFade(R.anim.fade_in,300);
        Logger.d("路径"+path);
        Glide.with(context).load(path).transition(opt).apply(options).into(imageView);
    }
    //加载图片
    public static void loadImageSmallIntoImageView(Context context, Object path, ImageView imageView) {
        RequestOptions options = new RequestOptions();
        options.diskCacheStrategy(DiskCacheStrategy.ALL);
        options.placeholder(R.mipmap.default_image);
        options.error(R.mipmap.default_image);
        options.centerCrop();//加载小图的时候使用
        DrawableTransitionOptions opt=new DrawableTransitionOptions();
        opt.crossFade(R.anim.fade_in,300);
        Glide.with(context).load(path).transition(opt).apply(options).into(imageView);


    }

7:TextView和EditText

情景1
需求:进入页面,textView默认提示点击选择textView填入内容,用户点击选择之后,回显用户选择的内容,往往默认提示内容和选择内容字体颜色是不一致的,如图:

1535452268(1).png

常规处理办法:手动设置改变文本和字体颜色setTextColor()和setText();
非常规处理办法:
a:xml文件中textView设置textColor()和HintTextColor(),默认提示内容写在hint()中;
b:java文件中用户点击选择输入内容后,setText()回显选择内容,或者清空选择内容setText("");
这种方式,同样能实现效果,就不用手动设置字体颜色了。

结束语

写完这篇文章说实话,感觉这些还是比较基础的一些东西,曾经感觉多难的东西,现在感觉基础了,或许这也叫一种成长。但是感觉自己的专业知识,无论从广度还是深度来讲,自己都远远达不到自己想要的水平,还停留再知道怎么用,不知道为啥这么用的阶段。写一句鞭策自己的话吧:在学习上,不要太容易满足,要知其然,也要知其所以然!!!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容