Android程序猿基本功

这里内容确实很多,可以使用ctrl+f搜索你所需要解决的,基本上都有,这里是笔记。

Jar包的使用

至于依赖库的选择个人认为因人而异,我使用的库如下:


butterknife————View注解库和配套插件android-butterknife-zelezny.
例@BindView(R.id.backdrop)
    ImageView mBackdrop;


retrofit+okhttp————网络请求相关


gson————google推荐的json数据解析库


glide————google推荐的Android图片加载库.包含缓存


RxAndroid+RxJava——Rx函数响应式编程中文文档


compile ‘com.android.support:design:23.0.1’————谷歌Material Design控件库


  //毛玻璃效果
        Glide.with(this)
                .load(MyApplication.currentGirl)
                .bitmapTransform(new BlurTransformation(this, 15))
                .into(mBackdrop);


https://github.com/SuperMan42/MVP


retrofit + okhttp + mvp +material desigin 这一套
做android 开发的人多不




在AND(&&)运算中a()&&b()&&c(),当a为false时,b与c都不再执行
Snackbar 和toast一个效果,从底部弹出 
一个产品开发新版本流程大概是:提出需求->根据需求UI做出效果图->然后产品和UI根据效果图做出小调整->定稿,UI切图->开发根据效果图开发
我们一般开发很多功能都有一个共用的概念,比如程序的标题栏等都是继承一个,如果效果不一样,我们就要单独处理,导致程序可维护性不高。
我理解的好的产品应该是: 设计简单、操作流畅、功能简单易用、稳定性高、用户体验好, 这些都很关键。


所以一些功能从产品经理设计出来的那一刻就注定是失败的,开发多努力都毫无意义...


1.我们尽量要参与需求的制定及讨论,尽量对一些不合理的地方及时从开发的角度提出自己的意见。


2.当需求制定出来的时候,我们拿到效果图,不要着急做,要脑子里面先想想哪里有问题,不合理,整体流程能不能跑通。想通了在做。


3.当产品上线前,尽量不要做一些没有太多意义的小功能。精力都放在处理关键bug,问题上。功能能不加就不加。


4.提前制定好计划,当需求反复改的时候,要及时和上级沟通交流,调整时间计划,避免后期时间计划对应不上。


缓存用的是glide,先缓存网址再用glide显示出来
















安卓四大组件:
Activity 
一个单独的屏幕 它上面可以显示一些控件也可以监听并处理用户的事件做出响应


Service  应用程序中不可见的“工人”


Content Provider 
提供共享的数据存储 Content Provider(内容提供器)用来管理和共享应用程序的数据库


Broadcast Receivers  ----广播
不包含任何用户界面 然而它们可以启动一个Activity以响应接受到的信息 或者通过NotificationManager通知用户


【View 】的基本概念
View是用户接口的基础构件 是所有widget类的基类 Widget类用于创建交互式UI构件(按钮,输入框等)
View 类的ViewGroup子类是layout 的基类 Layout是一个不可见的容器 它保存着View(或ViewGroup)并定义这些View的layout 属性
可以说View类是用户接口类中最重要的一个类


=======================================================================


一、监听 点击弹出内容


对象.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
       Toast.makeText(当前类.this,"",Toast.LENGTH_SHORT).show();
        }
   });


当前类.this---上下文 ""---弹出的内容 Toast.LENGTH_SHORT---时间长短


二、布局 布局可嵌套 不过尽量少用嵌套


1.LinearLayout 线性布局(最常用)


orientation="vertical" 纵向排序
orientation="horizontal" 横向排序 不写时默认横向
layout_weigh (权重) 占布局的几比几


2.RelativeLayout  相对布局(最常用)


layout_above="传入控件id" 在什么控件的上面
layout_below="传入控件id" 在什么控件的下面
layout_toRightOf="传入控件id" 在什么控件的右边


3.FrameLayout  桢布局 起点左上角 不能去改变


background      可以设置颜色 传图片
backgroundTint  给图片动态设置颜色(着色器)
foregroundGravity  前景位置
foreground     前景(永远在最前面)


4.TableLayout  表格布局  继承LinearLayout  也是一种线性布局


<TableRow/>每行都需要用到  自带横向布局


5.AbsoluteLayout  绝对布局(已过时)


layout_x 横坐标
layout_y 纵坐标


三、距离单位及边距


1.px(像素):每个px对应屏幕上的一个点
2.dip或dp:(设备独立像素):一种基于屏幕密度的抽象单位
在每英寸160点的显示器上,1dip=1px
3.sp(比例像素):主要处理字体的大小,可以根据用户的字体大小首选项进行缩放
4.PPI即每英寸所拥有的像素数目,所表示的是每英寸所拥有的像素(pixel)数目
( ppi/160 =getResources().getDisplayMetrics().scaledDensity)


px与dp相互转换公式
px = dp*ppi/160 
dp = px / (ppi / 160)
规定单位转换需要加上0.5f偏移值


四、距离单位及边距


layout_margin   外边距  控件距离父控件边界的距离
padding         内边距  控件内容距离控件边界的距离


五、常用控件 


控件对象.getText().toString().trim()返回一个去掉空格的字符串 trim()去掉前后空格


1.TextView   文本控件


android:textSize="15sp"  设置字体大小 用sp   sp=dp
android:gravity="center" 设置控件内容在什么位置 |后面还可以接位置
android:layout_gravity="center" 设置控件在父布局中的位置


2.ImageView  图片控件


android:src="@mipmap/ic_launcher"  图片的来源


3.EditText   输入框控件


android:inputType="textPassword"   密码可设置不可见
android:hint="手机号"              未输入之前显示的内容 输入后隐藏
android:singleLine="true"          不能换行输入
android:background="@null"         隐藏输入框下划线
android:textCursorDrawable="@null" 光标与字体颜色一致


4.RadioButton 单选框控件 必须用RadioGroup包裹 RadioButton必须指定id


可以通过RadioGroup对象.getCheckedRadioButtonId()获取RadioButton下的id


android:button="@null"  去掉○


<RadioGroup
   android:layout_width="match_parent"
   android:layout_height="45dp"
   android:orientation="horizontal">  //不写时默认纵向排序(vertical)


      <RadioButton
         android:id="@+id/radio1"
         android:layout_width="wrap_content"
         android:layout_height="45dp"
         android:text="男" />


      <RadioButton
         android:id="@+id/radio2"
         android:layout_width="wrap_content"
         android:layout_height="45dp"
         android:text="女" />
</RadioGroup>


5.CheckBox  复选框控件


android:text="我确认接受协议"  写入内容


6.Button   按钮控件


android:text="确认"  写入内容
android:focusable="false"  表示不抢别的控件的监听事件


7.Spinner   下拉框控件


8.ExpandableListView 嵌套listView 类似qq好友列表


适配器实现ExpandableListAdapter


9.SeekBar   可拉拽的进度条 类似手机音量


10.ProgressBar   下载进度条  默认转圈样式


style="@android:style/Widget.ProgressBar.Horizontal" 设置为水平进度条


★自定义适配器:


局部内部类方法:
//自定义Apdater ,继承自BaseAdapter
class SpinnerAdapter extends BaseAdapter{
    //数据显示的数量
    public int getCount() {
       if(list == null){
           return 0;
       }
       return list.size();
    }
    //当前这一行的数据,Spinner通过getSelectedItem()来获取
    public Object getItem(int position) {
       return list.get(position);
    }
    //当前位置
    public long getItemId(int position) {
       return position;
    }
    //spinner每一行显示的布局
    public View getView(int position, View convertView, ViewGroup parent) {
       View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.spinner_item,parent,false);
       TextView textView = (TextView) view.findViewById(R.id.textView1);
       textView.setText(list.get(position));
       return view;
     }
 }
//使用自定义Apdapter实现Spinner的数据关联
SpinnerAdapter adapter = new SpinnerAdapter();
spinner.setAdapter(adapter);


★adapter.notifyDataSetChanged(); 更新数据方法


系统适配器:ArrayAdapter  


Spinner spinner = (Spinner) findViewById(R.id.spinner);//spinner控件


//使用ArrayAdapter来实现Spinner控件
ArrayAdapter adapter = new ArrayAdapter        (MainActivity.this,R.layout.spinner_item,R.id.textView1,list)
上下文,布局文件资源(自己写的布局),布局的id,对象数组


//给Spinner控件设置适配器Adapter
spinner.setAdapter(adapter);


spinner.getSelectedItem().toString() 显示选择的数据内容


android.R.layout.simple_spinner_item
android.R.layout.simple_spinner_dropdown_item //系统自带布局(样式不同)


spinner.setSelection(position);  //根据传入的下标设置初始化位置


◆这种方法很少用:(内容写死了 不能动态修改)
android:entries="@array/city"   显示的内容


内容写到res下values里的strings.xml


<string-array name="city" >
   <item>长沙</item>
   <item>北京</item>
   <item>上海</item>
   <item>天津</item>
</string-array>


◆动态注册View,别加载到界面上
    LinearLayout lin = (LinearLayout) findViewById(R.id.lin);
    View view = LayoutInflater.from(this).inflate(R.layout.test2,null);
    View view2 = LayoutInflater.from(this).inflate(R.layout.activity_main,(ViewGroup) view,true);
    lin.addView(view2);


=======================================================================


【解析XML文件】XmlPullParser工具类可以快速方便的解析xml文件


XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();  //实例化XmlPullParser对象  XmlPullParserFactory工厂类


//把字符串转换成流 加载到XmlPullParser中
xmlPullParser.setInput(new StringReader(String));


//获取当前标签类型 一般以文档头开始
int type = parser.getEventType();


XmlPullParser.START_DOCUMENT  文档头
XmlPullParser.END_DOCUMENT    文档尾
XmlPullParser.START_TAG       开始标签
XmlPullParser.END_TAG         结束标签
XmlPullParser.TEXT            文本


pull.getEventType(); 返回当前解析的内容分类 刚开始指向文档头
pull.getName();  返回当前标签名
pull.getAttributeValue(下标); 返回当前标签指定下标的属性值
pull.nextText();  返回下一段文本
pull.next();  跳到下一解析部分
pull.setInput(FileOutputStream f, String Encode); 设置输入流 编码方式
pull.getText(); 返回当前文本


【Android数据存储】


1.SharedPreferences  它是一个轻量级的存储类 特别适合用于保存软件配置参数


SharedPreferences保存数据 其背后是用xml文件存放数据 
文件存放在/data/data/<package name>/shared_prefs目录下


可存储数据类型:
long 、boolean、int、float、String、double


初始化:
Context.getSharedPreferences(文件名,打开模式);
getSharedPreferences(name,mode)
方法的第一个参数用于指定该文件的名称 名称不用带后缀 后缀会由Android自动加上
方法的第二个参数指定文件的操作模式 共有四种操作模式


四种操作模式分别为:
1. MODE_APPEND: 追加方式存储
2. MODE_PRIVATE: 私有方式存储,其他应用无法访问  (常用)
3. MODE_WORLD_READABLE: 表示当前文件可以被其他应用读取
4. MODE_WORLD_WRITEABLE: 表示当前文件可以被其他应用写入


常用方法:
abstract boolean contains(String key) 检查文件中是否包含这个key


abstract SharedPreferences.Editor edit()
创建一个Editor对象 通过这个对象可以改变数据 注意的是数据被修改后需要使用函数commint()进行提交
Editor对象.putXX(K,V)  根据类型值储存数据 K--键 V--值


abstract Map<String,?> getAll()  获取文件中所有的数据


abstract boolean getXX(String key,XX defValue)
查找某个数据(如果没有对应值,返回默认值)


【SQLite数据库】 Android内置了一个轻量级的SqliteDatabase数据库


1.SQLite支持的数据类型
基本数据类型:
 NULL 空值
 INTEGER 带符号的整型 (常用)
 REAL 浮点型
 TEXT 字符串文本      (常用)
 BLOB 二进制对象
但实际上sqlite3也接受如下的数据类型:
 smallint 16 位元的整数
 interger 32 位元的整数
 decimal(p,s) p精确值和 s 大小的十进位整数 精确值p是指全部有几个数(digits)大小值 s是指小数点後有几位数 如果没有特别指定 则系统会设为 p=5; s=0
 float 32位元的实数
 double 64位元的实数


2.SQLite相关类


SQLiteOpenHelper抽象类:通过从此类继承实现用户类 来提供数据库创建、打开、关闭等操作
SQLiteDatabase数据库访问类:执行对数据库的插入记录、查询记录等操作。
SQLiteCursor查询结构操作类:用来访问查询结果中的记录


◆SQLiteOpenHelper
封装了创建和更新数据库使用的逻辑 SQLiteOpenHelper 的子类 一般需要实现如下三个方法


public DBHelper(Context context, String name, CursorFactory factory,int version) {}  //构造方法 可自己写一个构造方法 
例如:


    //数据库的名字
    private static final String sqlName = "test.db";
    //数据库的版本
    private static final int VERSION = 2;


    //构造方法,定义数据库名称和版本
    public MyTestSqlOpenHelper(Context context){
        this(context,sqlName,null,VERSION);
    }


public void onCreate(SQLiteDatabase db) {//使用execSQL()方法执行DDL语句}
//新建表 已存在不会重复 
例如:primary key autoincrement主键自动增长
db.execSQL("create table user(_id integer primary key autoincrement,"name text,age integer)");


public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  //修改表  Version是数据库版本号 只能增不能减
if (oldVersion<newVersion){
      db.execSQL("alter table user add address text");
   }
}


◆SQLite插入数据
使用SQLiteDatabase 对象的 insert(), update(), delete() 方法


ContentValues cv = new ContentValues(); //类似Map集合
    cv.put("name", “rose");
    cv.put("address", "ShangHai");
    db.insert(表名, null, cv);  //null表示没写入的地方显示null 返回那行id


◆SQLite查询数据  使用SQLiteDatabase 对象的query()方法


Cursor游标
getCount():获得结果集中有多少记录。
moveToFirst():移动到第一行。
moveToNext():移动到下一行,可遍历所有记录。
isAfterLast():判断是否最后一行。
getColumnNames():返回字段名。
getColumnCount():返回字段号。
getString(),getInt():传入想要获取的字段的下标位置,返回字段值
requery():重新执行查询得到游标。
getColumnIndex() : 传入字段名,返回当前字段下标位置
close():释放游标资源


getString()和getColumnIndex()一般放一起用


例如:
//表名,查询的数据,条件,条件赋值,排序等
Cursor cursor = db.query("user",new String[]{"name","age"},"name=? and age=?",new String[]{"胡桢","50"},null,null,null);


//查询所有可全设为null  
Cursor cursor = db.query("user",null,null,null,null,null,null);
List<Person> list = new ArrayList<>();
        //循环游标,获取数据
        while(cursor.moveToNext()){
            Person person = new Person();
            person.setId(cursor.getInt(cursor.getColumnIndex("_id")));
            person.setName(cursor.getString(cursor.getColumnIndex("name")));
            person.setAge(cursor.getInt(cursor.getColumnIndex("age")));
            list.add(person);
        }
        //释放游标,防止内存泄漏
        cursor.close();
        return list;


◆SQLiteDatabase
//插入、更新、删除数据需用到写入权限
SQLiteDatabase db = SQLiteOpenHelper对象.getWritableDatabase();
//查询需用到读取权限 但是读的时候能够获得写入权限
SQLiteDatabase db = SQLiteOpenHelper对象.getReadableDatabase();


【SD卡存储数据】


SD卡路径:
Environment.getExternalStorageDirectory().getAbsolutePath(); 


SD是否可用:
Environment.getExternalStorageState()   获取当前SD卡状态
Environment.MEDIA_MOUNTED SD卡可用


读取SD卡文件和向SD卡写入文件


例如:   与IO流类似


File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "/aaa.txt");
BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
            String str = "你好,--";
            bw.write(str);
            bw.flush();


            BufferedReader br = new BufferedReader(new FileReader(file));
            String str2 = "";
            StringBuffer sb = new StringBuffer();
            while ((str2 = br.readLine()) != null) {
                sb.append(str2);
            }
            textView.setText(sb.toString());


            bw.close();
            br.close();


========================================================================


【ListView】一种用于垂直显示的列表控件 如果显示内容过多 则会出现垂直滚动条


android:divider="@color/colorAccent" 线条颜色
android:dividerHeight="1dp"          线条高度
android:cacheColorHint="@null"       防止listview出现缓存导致黑屏现象 
android:scrollbars="none"            设置滚动条
android:listSelector="@color/colorAccent" item选中颜色


ListView列表的显示需要三个元素:
ListVeiw: 用来展示列表的View
适配器:用来把数据映射到ListView上的中介
数据:具体的将被映射的字符串,图片等


ListView在显示数据的时候可以设置布局方式,android系统提供了几种默认的布局:
android.R.layout.simple_list_item_1每个列表项只有一个TextView用来显示数据
android.R.layout.simple_list_item_2每个列表项有两个TextView用来显示数据
android.R.layout.simple_list_item_single_choice每个列表项后都有一个单选按钮
android.R.layout.simple_list_item_multiple_choice每个列表项后都有一个多选按钮
android.R.layout.simple_list_item_checked每个列表项后都有一个CheckedTextView


★ListView事件及监听


单击列表项时触发
public interface OnItemClickListener {
        void onItemClick(AdapterView<?> parent, View view, int position, long id);
    }
列表项被选择时触发
public interface OnItemSelectedListener {
void onItemSelected(AdapterView<?> parent, View view, int position, long id);
void onNothingSelected(AdapterView<?> parent);
    }
position为选中的列表项在ListView中的位置
id为被选中的那一行的id
parent指被单击的ListView
View代表用户选中的那一项


ListView组件不以使用那种布局作为单选或复选的标准,必须使用setChoiceMode()方法设置选取模式以后,单选复选才起作用(搭配系android系统提供的几种默认布局使用)
ListView.CHOICE_MODE_SINGLE  常量为1,表示单选
ListView.CHOICE_MODE_MULTIPLE 常量为2,表示复选
ListView.CHOICE_MODE_NONE 常量为0普通列表,无论使用了何种样式,单选复选都不起作用


★ListView适配器★


ListView适配器需要实现内存重复使用 可降低内存占用 防止内存溢出
前面与spinner适配器一样 差别在最后一个方法


public View getView(int position, View convertView, ViewGroup parent) {


      //定义一个ViewHolder 类对象 赋值为null
      ViewHolder viewHolder = null;


      //当(convertView 为空时 创建一个View
      if (convertView == null){


          //初始化ViewHolder 对象
          viewHolder = new ViewHolder();
          //初始化convertView 
          convertView = LayoutInflater.from(ListViewActivity.this).inflate(R.layout.adapter_item,parent,false);


          //初始化ViewHolder 类里的属性
          viewHolder.textView = (TextView) convertView.findViewById(R.id.text);
          viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);


          //用convertView.setTag()打包
          convertView.setTag(viewHolder);
      }else {
          //当convertView不为空时 给viewHolder赋值 不然后面会报空指针
          viewHolder = (ViewHolder) convertView.getTag();
      }


      //用viewHolder对象.属性设值
      viewHolder.textView.setText(list.get(position).get("text").toString());
      viewHolder.imageView.setBackgroundResource(Integer.parseInt(list.get(position).get("img").toString()));


      return convertView;
  }


//ViewHolder为了优化ListView内存 防止内存溢出 实现布局控件复用
class ViewHolder {
       TextView textView;
       ImageView imageView;
  }


【GridView】 和ListView一样 多了一个属性


android:numColumns="2"  设置多少列


=======================================================================


【RecyclerView】


只要用Android提供的LieanerLayoutManager并配以VERTICAL模式 RecyclerView就可以完美达到ListView的基本效果 两者的设计结构也都是数据(Dataset)与视图(View)分离 然后通过适配器(Adapter)来连接的方式


◆强制使用ViewHolder
◆没有OnItemClickListener
  开发者可以定义自己的OnItemClickListner来接受点击事件
◆视图与布局分离 
◆支持子项目层次的动画效果
  ItemAnimator接口


【RecyclerView实现】


◆适配器继承RecyclerView.Adapter
  getView方法不用自己写了 我们只需要写好Viewholder View的复用已经封装好了
◆LayoutManager 
  这个类决定视图被放在画面中哪个位置 简单来说就是管理布局 设置为LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager可以实现ListView,GridView以及流式布局的列表效果 它还可以管理滚动和循环利用
◆ItemAnimator
  这个类可以实现增删动画 而且不想设置的话它的默认效果已经很好了


◆需要配置compile'com.android.support:recyclerview-v7:23.1.1'


【例子】


final MyAdapter adapter = new MyAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);


//adapter调用自定义方法 实现接口的点击事件方法
adapter.setOnItemClickListener(new IOnItemClick() {


    public void onItemClickListener(View view, int position) {
        Toast.makeText(RecyclerActivity.this, "点击了第" + position + "个", Toast.LENGTH_SHORT).show();
    }
});


//adapter调用自定义方法 实现接口的长按事件方法
adapter.setOnItemLongClickListener(new IOnItemLongClick() {


    public void onItemLongClickListener(View view, int position) {
        Toast.makeText(RecyclerActivity.this, "长按了" + list.get(position), Toast.LENGTH_SHORT).show();
    }
});


button.setOnClickListener(new View.OnClickListener() {


    public void onClick(View v) {
        //点击按钮 删除数据
        list.remove(list.size()-1);
        //移除动画效果显示在最末尾
        adapter.notifyItemRemoved(list.size());
    }
});


button2.setOnClickListener(new View.OnClickListener() {


    public void onClick(View v) {
        //点击按钮新增数据
        list.add("i=="+list.size());
        //新增动画效果显示在最末尾
        adapter.notifyItemInserted(list.size());
    }
});
    }


//适配器 <>传入自己写的ViewHolder
class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private IOnItemClick onItemClick;
    private IOnItemLongClick onItemLongClick;


    //返回一个ViewHolder
    public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(RecyclerActivity.this).inflate(R.layout.activity_main_aidl, parent, false);
        MyViewHolder myViewHolder = new MyViewHolder(view);
        return myViewHolder;
    }


    //定义点击监听方法 传入接口
    public void setOnItemClickListener(IOnItemClick onItemClick) {
        this.onItemClick = onItemClick;
    }


    //定义长按监听方法 传入接口
    public void setOnItemLongClickListener(IOnItemLongClick onItemLongClick) {
        this.onItemLongClick = onItemLongClick;
    }


    //绑定ViewHolder 可以给控件设置属性
    public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
        holder.textView.setText(list.get(position));
    }




    public int getItemCount() {
        return list.size();
    }


    //定义一个ViewHolder继承RecyclerView.ViewHolder
    class MyViewHolder extends RecyclerView.ViewHolder {
         private TextView textView;


         //在构造方法里初始化控件
         public MyViewHolder(final View itemView) {
             super(itemView);
             textView = (TextView) itemView.findViewById(R.id.textView);


             //控件设置监听事件
             textView.setOnClickListener(new View.OnClickListener() {


                public void onClick(View v) {
                   //点击事件 调用接口的方法
                   if (onItemClick != null) {
                       onItemClick.onItemClickListener(itemView, getPosition());
                   }
                }
             });


             textView.setOnLongClickListener(new View.OnLongClickListener() {


                public boolean onLongClick(View v) {
                   //长按事件 调用接口的方法
                   onItemLongClick.onItemLongClickListener(itemView, getPosition());
                    return true;
                }
              });
         }
    }
}


//点击事件接口
public interface IOnItemClick {


    void onItemClickListener(View view, int position);
}
//长按事件接口
public interface IOnItemLongClick {


    void onItemLongClickListener(View view, int position);
}
=======================================================================


【Android消息处理机制】
Android是消息驱动的,实现消息驱动有几个要素:


消息的表示:Message
消息队列:MessageQueue
消息循环,用于循环取出消息进行处理:Looper
消息处理,消息循环从消息队列中取出消息后要对消息进行处理:Handler


平时我们最常使用的就是Message与Handler了


Handler、Message、Looper之间的关系:
在线程(Thread)中发送消息(Message) 进入消息队列(Message Queue) 通过Looper对象循环取出消息 并交给Handler进行处理消息


Message的创建:
1.直接new 一个message
2.Message.obtain() 防止重复创建


Looper对象的创建:
1.在子线程中需要手动初始化looper对象:Looper.prepare()启用Looper
public void run() {
    Looper.prepare();  //调用prepare后到调用loop类似于while循环
          
    handler = new Handler() {
       public void handleMessage(Message msg) {
           // process incoming messages here
       }
    };
          
    Looper.loop();
}
Looper.loop() 让Looper开始工作 从消息队列里取消息 处理消息
注意:写在Looper.loop()之后的代码不会被执行 这个函数内部应该是一个循环 当调用mHandler.getLooper().quit()后 loop才会中止 其后的代码才能得以运行


2.在主线程中默认创建Looper对象


【Handler】


handler = new Handler(){
      public void handleMessage(Message msg) {
          super.handleMessage(msg);
          if (msg.what==0){
              Toast.makeText(Main5Activity.this,msg.obj.toString(),Toast.LENGTH_SHORT).show();
          }
      }
};
new Thread(new Runnable() {
      public void run() {
          ★handler 发送消息的各种方式:


          //1、立即发送一个message.what的消息给hanlder
          handler.sendEmptyMessage(0);


          //2、延迟两秒后发送一个message.what的消息给hanlder
          handler.sendEmptyMessageDelayed(0,2000);


          //3、在当前时间+2秒后 发送一个message.what的消息给hanlder
          handler.sendEmptyMessageAtTime(0,System.currentTimeMillis()+2000);
          //4、直接使用Message对象进行封装消息并发送
          Message.obtain(handler,0,"我来自子线程!").sendToTarget();


          //5、直接使用handler对象进行封装消息并发送
          handler.obtainMessage(0,"我来自子线程!").sendToTarget();


          //6、最明了的Handler消息发送
          String str = "我来自子线程!";
          Message msg = new Message();
          msg.obj = str;
          msg.what = 0;
          handler.sendMessage(msg);
      }
}).start();


Handler可能引起内存泄漏:activity有一个onDestroy方法在此removehandler


protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
}


【RunOnUiThread】


调用runOnUiThread前面省略了一个this. 所以在fragment时调用时前面加一个getActivity. 或者getContext. 看情况


new Thread(new Runnable() {
      public void run() {
         while(probar.getProgress()<100){
            try {
               Thread.sleep(1000);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            count = probar.getProgress();
            count++;


            //在子线程调用runOnUiThread 可以直接在方法中进行ui操作
            runOnUiThread(new Runnable() {
               public void run() {
                  probar.setProgress(count);
                  textView.setText(probar.getProgress()+"");
               }
            });
         }
      }
}).start();


【AsyncTask异步任务】


AsyncTask是个抽象类 使用时需要继承这个类 然后调用execute()方法 
注意继承时需要设定三个泛型Params Progress和Result的类型 如AsyncTask<Void,Inetger,Void>


Params是指调用execute()方法时传入的参数类型和doInBackgound()的参数类型
Progress是指更新进度时传递的参数类型 即publishProgress()和onProgressUpdate()的参数类型
Result是指doInBackground()的返回值类型


上面的说明涉及到几个方法:
doInBackgound()这个方法是继承AsyncTask必须要实现的 运行于后台 耗时的操作可以在这里做
publishProgress()更新进度 给onProgressUpdate()传递进度参数
onProgressUpdate()在publishProgress()调用完被调用 更新进度


例如:


MyAsyncTask async = new MyAsyncTask();
async.execute();


class MyAsyncTask extends AsyncTask<Void, Integer, Void> {


    //异步任务 该方法运行在doInBackground之前 是在UI线程的
    protected void onPreExecute() {
       super.onPreExecute();
       Toast.makeText(AsyncTaskActivity.this, "开始任务", Toast.LENGTH_SHORT).show();
    }


    //后台运行 该方法主要进行耗时操作 不在UI线程
    protected Void doInBackground(Void... params) {
       while (count < 100) {
           count++;
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           publishProgress(count);
       }
       return null;
    }


    //在UI线程中 在doInBackground运行完后执行
    protected void onPostExecute(Void aVoid) {
       super.onPostExecute(aVoid);
       Toast.makeText(AsyncTaskActivity.this, "结束任务", Toast.LENGTH_SHORT).show();
    }


    //publishProgress()被调用后执行 publishProgress()用于更新
    protected void onProgressUpdate(Integer... values) {
       super.onProgressUpdate(values);
       values[0] = count;
       seekBar.setProgress(values[0]);
       textView.setText("已下载" + values[0] + "" + "%");
    }
}


【AsyncTask和Handler比较】


AsyncTask实现的原理和适用的优缺点:
AsyncTask是android提供的轻量级的异步类 可以直接继承AsyncTask 在类中实现异步操作 并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新) 最后反馈执行的结果给UI主线程
使用的优点:
简单、快捷
过程可控      
使用的缺点:
在使用多个异步操作和并需要进行Ui变更时 就变得复杂起来


Handler异步实现的原理和适用的优缺点:
在Handler 异步实现时 涉及到Handler、Looper、Message、Thread四个对象 实现异步的流程是主线程启动Thread(子线程)运行并生成Message-Looper获取Message并传递给HandlerHandler逐个获取Looper中的Message 并进行UI变更
使用的优点:
结构清晰 功能定义明确
对于多个后台任务时 简单、清晰
使用的缺点:
在单个后台异步处理时 显得代码过多 结构过于复杂(相对性)


=======================================================================


【Android网络请求】
Android常用的联网方式 包括JDK支持的HttpUrlConnection Apache支持的HttpClient 以及开源的一些联网框架


需要在AndroidManifest.xml注册:
<uses-permission android:name="android.permission.INTERNET"/>


【HttpUrlConnection】


handler = new Handler() {
     public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what == 0) {
             String str = msg.obj.toString();
             //解析Json
             GameBean game = analyticJson(str);


        }


     }
};


new Thread(new Runnable() {
   public void run() {
      try {
         //创建URL对象
         URL url = new URL("http://passport.7pa.com/mgame/list?pageSize=20&pageNum=1&cateid=1&typeid=1");
 
         //打开一个HttpURLConnection 连接
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         //设置请求方式
         conn.setRequestMethod("GET");
         //开始连接
         conn.connect();


         int code = conn.getResponseCode();
         //判断是否请求是否成功
         if (code == 200) {
             BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
             String str;
             sb = new StringBuffer();
             while ((str = br.readLine()) != null) {
                   sb.append(str);
                   sb.append("\n");
             }
             br.close();
             handler.obtainMessage(0, sb.toString()).sendToTarget();
          }
      } catch (Exception e) {
          e.printStackTrace();
      }
   }
}).start();


【Json解析】


{} 表示对象        使用JSONObject
[] 表示数组(集合)  使用JSONArray


例如:


JSONObject jo = new JSONObject(json);
gameBean.setCode(jo.getInt("code"));
gameBean.setMsg(jo.getString("msg"));


JSONArray ja = jo.getJSONArray("data");
List<GameBean.DataBean> list = new ArrayList<>();
for (int i = 0; i < ja.length(); i++) {
     GameBean.DataBean dataBean = new GameBean.DataBean();
     JSONObject object = ja.getJSONObject(i);


     dataBean.setId(object.getString("id"));
     dataBean.setName(object.getString("name"));
}


【第三方的网络请求库】


使用第三方Json解析:
public GameBean analyticJson(String json) {
    Gson gson = new Gson();
    GameBean gameBean = gson.fromJson(json, GameBean.class);
    return gameBean;
}
前面的GameBean 和后面GameBean.class要对应 (实体类类名.class )


★需要导包在libs 在项目右键open module settings→app→dependencies→+libs下面的gson-2.3.1.jar


优秀的第三方库:
https://github.com/square/okhttp                (常用)
https://github.com/kevinsawicki/http-request
https://github.com/wyouflf/xUtils3              (常用)
https://github.com/mcxiaoke/android-volley      (常用)
https://github.com/loopj/android-async-http


例如:使用xUtils3


使用Gradle构建时添加一下依赖即可:
compile 'org.xutils:xutils:3.3.36'


//首先初始化:新建一个MyApplication继承 Application
public class MyApplication extends Application {
    public void onCreate() {
        super.onCreate();
        x.Ext.init(this);
    }
}


需要在AndroidManifest.xml中在application中添加android:name=".MyApplication"


//请求的地址
RequestParams rp = new RequestParams("http://passport.7pa.com/mgame/list?pageSize=20&pageNum=1&cateid=1&typeid=1");


//x.http().get是get方式请求 new一个回调
x.http().get(rp, new Callback.CommonCallback<String>() {
      //请求成功得到Json
      public void onSuccess(String result) {
           //调用解析Json的方法
           bean = analyticJson(result);
           MyAdapter adapter = new MyAdapter();
           listView.setAdapter(adapter);
      }
      //请求出错
      public void onError(Throwable ex, boolean isOnCallback) {


      }
      //取消请求
      public void onCancelled(CancelledException cex) {


      }
      //请求完成
      public void onFinished() {


      }
});


//网络图片加载bean.getData().get(position).getIconurl()相当于图片网址
x.image().bind(imageView, bean.getData().get(position).getIconurl());


=======================================================================


【Android文件下载】


需要配置:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


◆原生态下载文件 写在线程里
new Thread(new Runnable() {


   public void run() {
      String downurl = "下载链接";
         try {


            //文件夹 前面的方法是获取SD卡路径+一个文件夹名
            File path = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/file");


            //判断文件夹是否存在 不存在则新建
            if (!path.exists()) {
                 path.mkdirs();
            }


            //创建一个文件名字
            File file = new File(path + "/" + bean.getData().get(position).getName() + ".apk");


            //类似IO流的方式下载一个文件 
            URL url = new URL(downurl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            InputStream is = conn.getInputStream();
            FileOutputStream fos = new FileOutputStream(file);


            //文件如果很大可以定的很大一点
            byte[] b = new byte[50 * 1024];
            while (is.read(b) != -1) {
                 fos.write(b);
            }
            fos.flush();
         } catch (Exception e) {
            e.printStackTrace();
         } finally {
            try {
                 if (is != null) {
                     is.close();
                 }
                 if (fos != null) {
                     fos.close();
                 }
            } catch (IOException e) {
                 e.printStackTrace();
            }
          }
    }
}).start();


◆第三方下载文件
RequestParams request = new RequestParams("下载链接");


//设置文件路径和文件名
request.setSaveFilePath("/sdcard/fileGame" + "/" + bean.getData().get(position).getName() + ".apk");


//回调 与网络请求回调有点区别
x.http().get(request, new Callback.ProgressCallback<File>() {


      //暂停下载会进这个方法
      public void onWaiting() {


      }


      //开始下载前会进这个方法
      public void onStarted() {


      }


      //正在下载时会进这个方法 可以写进度条控件
      public void onLoading(long total, long current, boolean isDownloading) {
        total总大小 current下载进度 isDownloading是否正在下载
      }


      //下载成功会进这个方法
      public void onSuccess(File result) {
           Toast.makeText(Json2Activity.this, "下载成功", Toast.LENGTH_SHORT).show();
      }


      //下载出错会进这个方法
      public void onError(Throwable ex, boolean isOnCallback) {


      }


      //取消下载会进这个方法
      public void onCancelled(CancelledException cex) {


      }


      //下载完成会进这个方法
      public void onFinished() {


      }
});


=======================================================================


【Activity介绍】


Activity是Android的四大组件之一 是应用程序加载布局的容器
一个Activity通常就是一个单独的屏幕 Activity之间通过Intent进行通信


●生命周期:
Activity starts开始时
     ↓
     ↓
进入onCreate()←←←←←←←←←←←←←←←←←←←←←←←←←←←←
     ↓                                                           ↑
     ↓                                                           ↑
进入onStart()←←←←←←←←←←←←←←←←←←←←←←←        ↑
     ↓                                                 ↑        ↑
     ↓                                                 ↑        ↑
进入onResume()   Activity正在运行 如果跳到另一个界面     ↑        ↑
     ↓   ↑                                             ↑        ↑
     ↓   ↑ 如果很快又回到之前界面重新进入onResume()     ↑        ↑
进入onPause()                                           ↑        ↑
     ↓  如果长时间没返回                               ↑        ↑
     ↓                                                 ↑        ↑
进入onStop()→→→之前界面又重新回到当前界面→→→进入onRestart(    ↑
     ↓  ↓  其他程序需要更多内存时会关闭此Activity 如果重新打开时  ↑
     ↓   →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→
     ↓   Activity运行结束
进入onDestroy()
     ↓
     ↓
Activity is shut down 程序关闭


【Activity显式意图界面跳转】


第一个界面:
Intent intent = new Intent(MyActivity.this,JumpTestActivity.class);
intent.putExtra("text","来自第一个界面的数据");
startActivityForResult(intent,0); //需要返回数据 0表示请求标示
startActivity(intent);    //单纯跳转


protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode==0&&resultCode==2){
         Toast.makeText(MyActivity.this,data.getStringExtra("result"),Toast.LENGTH_SHORT).show();
    }
}


第二个界面:
Intent intent = getIntent(); //拿到第一个界面传过来的值 
    if (intent != null){
        textView.setText(intent.getStringExtra("text"));
    }


button.setOnClickListener(new View.OnClickListener() {


    public void onClick(View v) {
        Intent intent2 = new Intent(JumpTestActivity.this,MyActivity.class);
        intent2.putExtra("result","来自第二个界面的返回数据");
        //设置返回标示 和数据
        setResult(2,intent2);
        //关闭当前activity
        finish();
    }
});


配置文件两个Activity都要注册 第一个界面的Activity写在主入口


【Activity隐式意图界面跳转】


第一个程序:
Intent intent = new Intent();
//跳转到第二个程序 传第二个程序的action名字
intent.setAction("action.my.other");
startActivity(intent);


//传类别的名字
intent.addCategory(android.intent.category.HOME);


配置文件:
//设置此程序为主界面 按home键时 模拟系统应用
<category android:name="android.intent.category.HOME"/>
//这个属性一定要写 表示类别默认
<category android:name="android.intent.category.DEFAULT"/>


第二个程序的配置文件:
<action android:name="action.my.other" />
<category android:name="android.intent.category.DEFAULT"/>


Bundle bundle = new Bundle();
bundle.put("K",V);    //以键值对的形式存数据
bundle.getXX("K","M");  //按照键去找值 没有时返回一个默认M值


【Activity状态保持】


使用onSaveInstanceState和onRestoreInstanceState来进行保存


protected void onSaveInstanceState(Bundle outState) {
     super.onSaveInstanceState(outState);
     outState.putString("key","有数据");
}


protected void onRestoreInstanceState(Bundle savedInstanceState) {
     super.onRestoreInstanceState(savedInstanceState);
     Toast.makeText(this,savedInstanceState.getString("key","没数据"),Toast.LENGTH_SHORT).show();
}


【任务堆栈】


Android中的任务堆栈是指与用户交互的一组Activity的集合 Activity会被按打开顺序安排在一个堆栈里


在配置文件中activity添加一个属性:
android:launchMode=四种模式


标准模式(默认):
●Standard :每次激活Activity时都会创建Activity 并放入任务栈中


独享任务栈顶(浏览器书签):
●SingleTop :如果在任务的栈顶正好存在该Activity的实例就重用该实例 否则就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例 只要不在栈顶都会创建实例)


独享任务堆栈:
●SingleTask:如果在栈中已经有该Activity的实例就重用该实例(会调用实例的onNewIntent()) 重用时会让该实例回到栈顶 因此在它上面的实例将会被移除栈 如果栈中不存在该实例 将会创建新的实例放入栈中


单例模式:
●SingleInstance:在一个新栈中创建该Activity实例 并让多个应用共享该栈中的该Activity实例 一旦该模式的Activity的实例存在于某个栈中 任何应用再激活该Activity时都会重用该栈中的实例 其效果相当于多个应用程序共享一个应用 不管谁激活该Activity都会进入同一个应用中


如:通话界面:在哪里激活都是同一个界面


=======================================================================


【Service简介】


Service(服务)是一个没有用户界面的在后台运行执行耗时操作的应用组件


其他应用组件能够启动Service 并且当用户切换到另外的应用场景 Service将持续在后台运行


另外 一个组件能够绑定到一个service与之交互(IPC机制) 例如一个service可能会处理网络操作、播放音乐、操作文件I/O或者与内容提供者(content provider)交互 所有这些活动都是在后台进行


【Service启动方式】


通过startService()启动的服务处于“启动的”状态 一旦启动service就在后台运行 即使启动它的应用组件已经被销毁了 当下载或上传一个文件 当这项操作完成时 service应该停止它本身


还有一种“绑定”状态的service 通过调用bindService()来启动 一个绑定的service提供一个允许组件与service交互的接口 可以发送请求、获取返回结果 还可以通过跨进程通信来交互(IPC) 绑定的service只有当应用组件绑定后才能运行 多个组件可以绑定一个service 当调用unbind()方法时 这个service就会被销毁了
★一个Activity只绑定一次


【Service生命周期】


●启动服务
该服务在其他组件调用 startService() 时创建 然后无限期运行 且必须通过调用 stopSelf() 来自行停止运行 此外其他组件也可以通过调用 stopService() 来停止服务 服务停止后 系统会将其销毁


startService()→→onCreate()→→onStartCommand()→→onDestroy()


●绑定服务
该服务在另一个组件(客户端)调用 bindService() 时创建 然后客户端通过IBinder 接口与服务进行通信 客户端可以通过调用 unbindService() 关闭连接 多个客户端可以绑定到相同服务 而且当所有绑定全部取消后 系统即会销毁该服务(服务不必自行停止运行)


startService()→→onCreate()→→onBind()→→onUnbind()→→onDestroy()


【Service启动】


Intent intent = new Intent(this,MyService.class);
intent.putExtra("key","服务启动");
//启动服务
startService(intent);
//关闭服务
stopService(intent);


//写在Service里
public int onStartCommand(Intent intent, int flags, int startId) {
     //使用startService启动服务会进入该方法
     Toast.makeText(this,intent.getStringExtra("key"),Toast.LENGTH_SHORT).show();
     stopSelf();
     return super.onStartCommand(intent, flags, startId);
}






//启动服务通过bind方式
三种静态标志:
BIND_AUTO_CREATE 表示当收到绑定请求时 如果服务尚未创建 则即刻创建 在系统内存不足 需要先销毁优先级组件来释放内存 且只有驻留该服务的进程成为被销毁对象时 服务才可被销毁


BIND_DEBUG_UNBIND 通常用于调试场景中判断绑定的服务是否正确 但其会引起内存泄漏 因此非调试目的不建议使用


BIND_NOT_FOREGROUND 表示系统将阻止驻留该服务的进程具有前台优先级 仅在后台运行
bindService(intent,conn,BIND_AUTO_CREATE);


ServiceConnection conn = new ServiceConnection() {


   public void onServiceConnected(ComponentName name, IBinder service) {
        //接收服务中onBind方法的返回值
        MyService.MyIBinder binder = (MyService.MyIBinder) service;
            binder.prompt();
   }


   public void onServiceDisconnected(ComponentName name) {


   }
};


protected void onDestroy() {
     super.onDestroy();
     unbindService(conn);
}


//写在Service里
public IBinder onBind(Intent intent) {
    // TODO: Return the communication channel to the service.
    //通过bindService启动服务会进入该方法
    //使用Binder对象进行数据的传递
    MyIBinder myIBinder = new MyIBinder();
        return myIBinder;
}


class MyIBinder extends Binder {
    public String prompt(){
        return "启动服务";
    }
}


【IntentService简介】


IntentService是Service类的子类 用来处理异步请求
客户端可以通过startService(Intent)方法传递请求给IntentService 
IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来处理所有Intent请求对象(通过startService的方式发送过来的)所对应的任务 这样以免事务处理阻塞主线程 执行完所一个Intent请求对象所对应的工作之后 如果没有新的Intent请求达到 则自动停止Service 否则执行下一个Intent请求所对应的任务


IntentService在处理事务时 还是采用的Handler方式 创建一个名叫ServiceHandler的内部Handler 并把它直接绑定到HandlerThread所对应的子线程 ServiceHandler把处理一个intent所对应的事务都封装到叫做onHandleIntent的虚函数 因此我们直接实现虚函数onHandleIntent 再在里面根据Intent的不同进行不同的事务处理就可以了


startService(intent);


public class MyIntentService extends IntentService {


    public MyIntentService() {
        super("MyIntentService");
    }


    @Override
    protected void onHandleIntent(Intent intent) {
        //自带线程,可以进行耗时操作
    }


}


【AIDL】


Android系统中的各应用程序都运行在各自的进程中 进程之间通常是无法直接交换数据的
      
Android提供了跨进程调用Service的功能 称为AIDL(android interface define language)Android接口定义语言
      
ADIL相当与两个进程通信的协议 通过这个协议对进程间的通信进行了规范 按照该规范编写代码即可实现进程间的通信


◆AIDL 接口文件
      跨进程调用服务中需要定义接口文件 扩展名为.aidl
      1、在项目的Main文件夹下定义一个AIDL接口文件
      2、AIDL接口的内部语法与Java很相似
      3、.aidl接口文件创建后,需要重新构建Gradle 这样会在build/generated/source/aidl文件下自动生成相应的文件


★只适用于使用bindService()启动的Service


●在一个程序里两个进程:


//AIDL接口
package com.example.administrator.mydemo;


interface IMyAidlInterface {
    String text(); //接口中的方法
}


//Service
public IBinder onBind(Intent intent) {
    return stub;
}
IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {


    public String text() throws RemoteException {
         return "我想回家睡觉= =";
    }
};


//Activity
Intent intent = new Intent(MainAidlActivity.this,MyAidlService.class);
bindService(intent,conn,BIND_AUTO_CREATE);


ServiceConnection conn = new ServiceConnection() {


   public void onServiceConnected(ComponentName name, IBinder service) {
       IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
       try {
           textView.setText(iMyAidlInterface.text());


       } catch (RemoteException e) {
           e.printStackTrace();
       }
   }




   public void onServiceDisconnected(ComponentName name) {


   }
};


//配置文件
<service
    android:name=".MyAidlService"
    android:process=":test">     //表示不在一个进程内容:+自定义
</service>


●在两个程序里:


//程序一中的Activity
Intent intent = new Intent("service.application");
bindService(intent,conn,BIND_AUTO_CREATE);


ServiceConnection conn = new ServiceConnection() {


   public void onServiceConnected(ComponentName name, IBinder service) {
       IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
       try {
           textView.setText(iMyAidlInterface.text());


       } catch (RemoteException e) {
           e.printStackTrace();
       }
   }




   public void onServiceDisconnected(ComponentName name) {


   }
};


//程序二中的Service
public IBinder onBind(Intent intent) {
    return stub;
}


IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {


    public String text() throws RemoteException {
        return "我想看机械师2!!!";
    }
};




//程序二中的Service的配置文件
<service
    android:name=".MyService">
    <intent-filter>
         <action android:name="service.application"/>
    </intent-filter>
</service>


★两个程序中的AIDL接口必须一样(方法一致) 包的文件夹名字也必须相同
========================================================================


【ContentProvider内容提供者】
当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。


应该明确以下几点:
1.在系统里不同程序之间共享数据
2.不同程序之间交换数据的标准API
3. ContentProvider以Uri的形式对外提供数据 其他程序使用ContentResolver根据Uri去操作指定的数据
4.四大组件之一 必须要在AndroidManifest.xml进行配置
5.一旦程序通过ContentProvider暴露自己的数据操作接口 不管程序是否启动 其他程序都可以通过该接口访问或操作该程序的内部数据
6.增、删、查、改四种数据操作方式


【Uri类】


Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")
这种URI由3个部分组成: 
“content://”、代表数据的路径、和一个可选的标识数据的ID


URI也可以写成如下形式:
  Uri person = ContentUris.withAppendedId(People.CONTENT_URI,45);




【例子】


◆自己定义一个ContentProvider


private MySql mySql;
//定义一个UriMatcher 类
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int USERS = 1;
private static final int USER = 2;


//UriMatcher 类有添加uri的方法
static {
    MATCHER.addURI("com.xiaoairen.test", "user", USERS);
    MATCHER.addURI("com.xiaoairen.test", "user/#", USER);//#占位符 int值
}


插入数据:
public Uri insert(Uri uri, ContentValues values) {
     SQLiteDatabase db = mySql.getWritableDatabase();
     long id = db.insert("user", null, values);


     return ContentUris.withAppendedId(uri, id); //返回一个uri
}


新建数据库:
public boolean onCreate() {
     mySql = new MySql(this.getContext()); //这里上下文需用到getContext()
     return false;
}


查询数据:
public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
     SQLiteDatabase db = mySql.getReadableDatabase();
     Cursor cursor = null;
     //匹配uri
     switch (MATCHER.match(uri)) {
        case USERS:
            cursor = db.query("user", projection, selection, selectionArgs, null, null, sortOrder);
            break;


        case USER:
            //截取字符串 获得最后的int值 可用string方法里的substring()
            long id = ContentUris.parseId(uri);
            //自定义一个条件
            String where = "";
   //如果传过来没有额外条件 只根据int值id去查询数据
            if (selection.equals("")) {
                 where = "_id = " + id;
   //如果传过来有额外条件 根据条件+int值id去查询数据
            } else {
                 where = selection + "and _id = " + id;
            }
            cursor = db.query("user", projection, where, selectionArgs, null, null, sortOrder);
            break;
     }


     return cursor;
}


◆Actitvity里


//调用ContentProvider
ContentResolver resolver = getContentResolver();
//封装uri 向数据库插入数据
Uri uri = Uri.parse("content://com.xiaoairen.test");
ContentValues cv = new ContentValues();
cv.put("name","石佛");
cv.put("age",35);
resolver.insert(uri,cv);


//查询数据
Uri uri2 = Uri.parse("content://com.xiaoairen.test/user");
//不传条件则查询所有
Cursor cursor = resolver.query(uri2,null,null,null,null);
if (cursor != null){
     StringBuffer sb = new StringBuffer();
     while (cursor.moveToNext()){
          sb.append("姓名:");
          sb.append(cursor.getString(cursor.getColumnIndex("name")));
          sb.append("\n");
          sb.append("年龄:");
          sb.append(cursor.getInt(cursor.getColumnIndex("age")));
     }
     textView.setText(sb.toString());
}


//会查询id为1的数据 还可以另加条件
Uri uri3 = Uri.parse("content://com.xiaoairen.test/user/1");
Cursor cs = resolver.query(uri3,null,"",null,null);
if (cs != null){
     StringBuffer sb = new StringBuffer();
     while (cs.moveToNext()){
          sb.append("姓名:");
          sb.append(cs.getString(cs.getColumnIndex("name")));
     }
     Toast.makeText(this,sb.toString(),Toast.LENGTH_SHORT).show();
}


◆配置文件
//建立ContentProvider时会自动生成
<provider
      android:name=".MyContentProvider"
      android:authorities="com.xiaoairen.test" >//自己传入的uri
</provider>


//权限注册
<uses-permission android:name="READ_USER_DICTIONARY" />
<uses-permission android:name="WRITE_USER_DICTIONARY" />


=========================================================================


【BroadcastReceiver】


本质上就是一个全局的监听器 用于监听系统全局的广播消息 由于BroadcastReceiver是一种全局的监听器 因此它可以方便地实现系统中不同组件之间的通信


程序通过调用Context的sendBroadcast()方法来启动指定的BroadcastReceiver


(1)在程序组件中构建要广播的Intent 使用sendBroadcast()方法发送出去


(2)定义一个自己的广播接收器 这个广播接收器需要继承BroadcastReceiver类 然后重写onReceiver()方法来响应事件


(3)注册广播接收器,可以使用两种方式注册:
//动态注册
IntentFilter filter = new IntentFilter("test.my.receiver");
MyReceiver receiver = new MyReceiver();
registerReceiver(receiver,filter);


//静态注册
<receiver android:name=".MyReceiver2">
    <intent-filter>
         <action android:name="test.my.receiver" />
    </intent-filter>
</receiver>


【生命周期】


即每次系统广播事件发生后 系统就会创建相应的BroadcastReceiver的实例 并自动触发它的onReceiver()方法 该方法执行完后 BroadcastReceiver的实例就会被销毁


如果BroadcastReceiver的onReceiver()方法不能在10秒内执行完成 Android会认为该程序无响应 所以不要在BroadcastReceiver的onReceiver()方法中执行耗时的操作 否则会弹出ANR的对话框


如果确实需要根据Broadcast来完成比较耗时的任务 则可以考虑通过Intent启动一个Service来完成操作


【两种BroadcastReceiver】


(1)普通广播:普通广播可以在同一时刻被所有接收者接收到 消息传递的效率比较高 但缺点是接收者不能将处理结果传递给下一个接收者 并且无法终止Broadcast Intent的传播


例如:
//Activity
Intent intent = new Intent();
intent.setAction("test.my.receiver");
intent.putExtra("key","广播");
sendBroadcast(intent);


//Receiver
public void onReceive(Context context, Intent intent) {
       
     Toast.makeText(context, intent.getStringExtra("key"), Toast.LENGTH_SHORT).show();


}


(2)有序广播:Ordered Broadcast的接收者将按预先声明的优先级一次接收Broadcast 如:A的优先级高于B B的优先级高于C 那么Broadcast先传给A 再传给B 最后传给C 优先级声明在<intent-filter…/>元素中的android:priority属性中 数越大优先级别越高 取值范围在-1000~1000 优先级别也可以调用IntentFilter对象的setPriority()方法进行设置
Ordered Broadcast接收者可以终止Broadcast Intent的传播 Broadcast Intent的传播一旦终止 后面的接收者就无法收到Broadcast 另外 Ordered Broadcast的接收者可以将数据传递给下一个接收者 如:A得到Broadcast后 可以往它的结果对象中存入数据 当Broadcast传给B时 B可以从A的结果对象中得到A存入的数据


例如:
//Activity
Intent intent = new Intent();
intent.setAction("test.my.receiver");
intent.putExtra("key2","有序广播");
sendOrderedBroadcast(intent,null);


//Receiver
public void onReceive(Context context, Intent intent) {


     Toast.makeText(context, intent.getStringExtra("key"), Toast.LENGTH_SHORT).show();


     Bundle bundle = new Bundle();
     bundle.putString("first","第一个存入数据");
     setResultExtras(bundle);
}


//Receiver2
public void onReceive(Context context, Intent intent) {
     Bundle bundle = getResultExtras(true);
     Toast.makeText(context,intent.getStringExtra("key2")+"\n"+bundle.getString("first"),Toast.LENGTH_SHORT).show();
}


//配置文件
<receiver android:name=".MyReceiver">
    <intent-filter android:priority="10">
        <action android:name="test.my.receiver" />
    </intent-filter>
</receiver>


<receiver android:name=".MyReceiver2">
    <intent-filter android:priority="0">
        <action android:name="test.my.receiver" />
    </intent-filter>
</receiver>


【系统广播】


下面是android系统中定义了很多标准的Broadcast Action来响应系统的广播事件


//表示电池电量低
Intent.ACTION_BATTERY_LO;


//表示电池电量充足
Intent.ACTION_BATTERY_OK;


//在系统启动完成后,这个动作被广播一次(只有一次)。
Intent.ACTION_BOOT_COMPLETED;


//重启设备时的广播
Intent.ACTION_REBOOT;


//屏幕被关闭之后的广播
Intent.ACTION_SCREEN_OFF;


//屏幕被打开之后的广播
Intent.ACTION_SCREEN_ON;


//关闭系统时发出的广播
Intent.ACTION_SHUTDOWN;


例如:
//Activity
Intent intent = new Intent();
intent.setAction("test.my.receiver");
intent.putExtra("key","广播");
sendBroadcast(intent);


//Receiver
public void onReceive(Context context, Intent intent) {
   if ("test.my.receiver".equals(intent.getAction())){
       Toast.makeText(context, intent.getStringExtra("key"), Toast.LENGTH_SHORT).show();
   }else {
       Toast.makeText(context,"开机了",Toast.LENGTH_SHORT).show();
   }
}


//配置文件
<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="test.my.receiver" />
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>


响应系统广播需要注册相应权限: 
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>


========================================================================


【SurfaceView】


它拥有独立的绘图表面 即它不与其宿主窗口共享同一个绘图表面 SurfaceView的UI就可以在一个独立的线程中进行绘制 不会占用主线程资源 SurfaceView一方面可以实现复杂而高效的UI 另一方面又不会导致用户输入得不到及时响应


【自定义】
使用Camera控制拍照的几个步骤:


1、调用Camera的open()打开相机


2、调用Camera的getParameters()获取拍照参数 该方法返回一个Camera.Paremeters对象


3、调用Camera.Parameters对象方法设置拍照的参数


4、调用Camera.startPreview()方法开始预览取景 在预览取景之前需要调用Camera的setPreviewDisplay(SurfaceHolder holder)方法设置使用哪个SurfaceView来显示取景图片


5、调用Camera的takePicture()方法进行拍照


6、结束程序时 调用Camera的stopPreview()结束取景预览 并调用release()方法释放资源


【权限】


<!-- 调用摄像头权限 -->
<uses-permission android:name="android.permission.CAMERA" />


<!-- 录制视频/音频权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />


<!-- sd卡读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


<!-- 挂载sd卡 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
 
【调用系统相机】


//照相
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 3);


protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   //Activity.RESULT_OK =-1 表示请求成功
   if (requestCode == 3 && resultCode == Activity.RESULT_OK) {


      //Bitmap用来加载图片 非常耗内存资源 bitmap.recycle()回收bitmap
      Bitmap bitmap = (Bitmap) data.getExtras().get("data");
      File path = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/mycamera");
      if (!path.exists()) {
           path.mkdirs();
      }
      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss");
      File file = new File(path + "/" + sdf.format(new Date()) + ".jpg");


      try {
           fos = new FileOutputStream(file);
           //把数据写入文件
           bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           try {
               if (fos != null) {
                   fos.close();
               }
           } catch (IOException e) {
               e.printStackTrace();
           }
      }


      Toast.makeText(Activity.this,"拍摄成功", Toast.LENGTH_SHORT).show();
   } else {
      Toast.makeText(Activity.this,"没有拍摄", Toast.LENGTH_SHORT).show();
   }
}


//录视频
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
startActivityForResult(intent, 3);


protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == 3 && resultCode == Activity.RESULT_OK) {
          
      Uri uri = data.getData();
      ContentResolver resolver = getContentResolver();
      Cursor cursor = resolver.query(uri, null, null, null, null);
      if (cursor.moveToNext()){
         //储存的路径
         String str = cursor.getString(cursor.getColumnIndex("_data"));
         Toast.makeText(Activity.this,"拍摄成功", Toast.LENGTH_SHORT).show();
      }


   } else {
      Toast.makeText(Activity.this,"没有拍摄", Toast.LENGTH_SHORT).show();
   }
}


=========================================================================


【补间动画(Tween)】
Android 平台提供了两类动画 一类是Tween动画,就是对场景里的对象不断的进行图像变化来产生动画效果(旋转、平移、放缩和渐变)


主要类:
 
Animation   动画
AlphaAnimation 渐变透明度
RotateAnimation 画面旋转
ScaleAnimation 渐变尺寸缩放
TranslateAnimation 位置移动
AnimationSet 动画集


//起始渐变透明 
Animation animation = new AlphaAnimation(0.1f,1.0f);
//起始角度旋转
Animation animation2 = new RotateAnimation(0.0f,360.0f);
//X、Y轴的起始伸缩
Animation animation3 = new ScaleAnimation(1.0f,0.1f,0.1f,1.0f);
//X、Y轴的起始移动
Animation animation4 = new TranslateAnimation(0.1f,200.0f,0.1f,300.0f);


AnimationSet set = new AnimationSet(true);
set.addAnimation(animation);
set.addAnimation(animation2);
set.addAnimation(animation3);
set.addAnimation(animation4);
set.setDuration(3000);
set.setRepeatCount(2);
set.setFillAfter(true);
imageView.startAnimation(set);


Animation.setDuration(3000);//动画持续时间
Animation.setRepeatCount(3);//设置重复次数
Animation.setFillAfter(true);//动画执行完后是否停留在执行完的状态
Animation.setStartOffset(2000);//执行前的等待时间


还可以在XML操作:
在res下创建一个anim文件 然后新建这些xml set标签
<alpha />      //渐变
<rotate />     //旋转
<scale />      //缩放
<translate />  //移动 X轴以下是100%p 以上是-100%p 从底部计算
例如:
Animation animation = AnimationUtils.loadAnimation(上下文,R.anim.alpha);
imageView.startAnimation(animation);


【帧动画(Frame)】


Frame动画 即顺序的播放事先做好的图像 与gif图片原理类似


把图片放在drawable下 新建一个xml  animation-list
//oneshot 表示是否循环一次 false表示一直循环
例如:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/load0" android:duration="100"/>
    <item android:drawable="@drawable/load1" android:duration="100"/>
    <item android:drawable="@drawable/load2" android:duration="100"/>
    <item android:drawable="@drawable/load3" android:duration="100"/>
    <item android:drawable="@drawable/load4" android:duration="100"/>
    <item android:drawable="@drawable/load5" android:duration="100"/>
    <item android:drawable="@drawable/load6" android:duration="100"/>
</animation-list>


//ImageView控件:android:background="@drawable/animation"
AnimationDrawable drawable = (AnimationDrawable) imageView.getBackground();
drawable.start();


【属性动画】


Property Animation就是通过动画的方式改变对象的属性了 我们首先需要了解几个属性:


相关的类:


ObjectAnimator     动画的执行类
ValueAnimator      动画的执行类 
AnimatorSet        用于控制一组动画的执行:线性,一起,每个动画的先后执行等
AnimatorInflater   用户加载属性动画的xml文件
TypeEvaluator      类型估值 主要用于设置动画操作属性的值
TimeInterpolator   时间插值 定义动画的变化率


总的来说属性动画就是 动画的执行类来设置动画操作的对象的属性、持续时间、开始和结束的属性值、时间差值等 然后系统会根据设置的参数动态的变化对象的属性


// 重复次数 -1表示无限循环
animator.setRepeatCount(-1);


// 重复模式, RESTART: 重新开始 REVERSE:恢复初始状态再开始
animator.setRepeatMode(ValueAnimator.REVERSE);


/**动画变化率 Interpolator
 * DecelerateInterpolator  动画开始时比较慢 然后继续减速
 * AccelerateDecelerateInterpolator 动画开始和结束时比较慢 中间速度较快
 * AccelerateInterpolator  动画开始时比较慢 之后加速
 * CycleInterpolator  传入int参数 决定动画循环几次 速率改变为正弦曲线
 * LinearInterpolator 匀速
 */
anim.setInterpolator(new DecelerateInterpolator());
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.setInterpolator(new AccelerateInterpolator());
anim.setInterpolator(new CycleInterpolator(3));
anim.setInterpolator(new LinearInterpolator());


【ObjectAnimator】


//参数二为对象属性 该属性需要有set和get方法
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView,"rotationX",0.0f,360.0f).setDuration(3000);
animator.start();


animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {


    public void onAnimationUpdate(ValueAnimator animation) {
        //获取转换后0到1的float值
        float fraction = animation.getAnimatedFraction();
        //获取当前设置的值
        float value = (float) animation.getAnimatedValue();
        imageView.setRotationY(value);
        imageView.setAlpha(fraction);
        imageView.setScaleX(fraction);
        imageView.setScaleY(fraction);
        imageView.setTranslationX(value);
    }
});


//写在xml里 在res文件下新建animator文件
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="1000"
        android:propertyName="rotationY"
        android:valueFrom="0"
        android:valueTo="360" >
    </objectAnimator>
    <objectAnimator
        android:duration="1000"
        android:propertyName="alpha"
        android:valueFrom="0"
        android:valueTo="1" >
    </objectAnimator>
    <objectAnimator
        android:duration="1000"
        android:propertyName="translationY"
        android:valueFrom="0"
        android:valueTo="300" >
    </objectAnimator>
</set>


Animator animator = AnimatorInflater.loadAnimator(MainActivity.this,R.animator.animtion);
animator.setTarget(imageView);
animator.start();


【ValueAnimator】
ObjectAnimator和ValueAnimator的区别之处:ValueAnimator并没有在属性上做操作


好处:不需要操作的对象的属性一定要有getter和setter方法 你可以自己根据当前动画的计算值 来操作任何属性


ValueAnimator animator = ValueAnimator.ofFloat(0.1f,330.0f);
animator.setTarget(imageView);
animator.setDuration(3000).start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {


    public void onAnimationUpdate(ValueAnimator animation) {
        float fraction = animation.getAnimatedFraction();
        float value = (float) animation.getAnimatedValue();
        imageView.setTranslationY(value);
        imageView.setTranslationX(value);
        imageView.setRotationY(value);
        imageView.setScaleX(fraction);
        imageView.setScaleY(fraction);
    }
});


【AnimatorSet】


ObjectAnimator anim = ObjectAnimator.ofFloat(imageView, "rotationY", 0.0f, 360.0f);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f, 1.0f);
ObjectAnimator anim3 = ObjectAnimator.ofFloat(imageView, "translationY", 0.0f, 300.0f);


AnimatorSet set = new AnimatorSet();
set.setDuration(3000);
set.setTarget(imageView);


//按顺序执行
set.playSequentially(anim, anim2, anim3);


// 同时执行动画
set.playTogether(anim, anim2, anim3);


// 动画anim和anim2同时执行,然后执行anim3,不能写为set.play(anim).with(anim2).after(anim3);
set.play(anim).with(anim2);
set.play(anim3).after(anim2);


// 动画anim和anim2同时执行,然后执行anim3,不能写为set.play(anim).with(anim2).before(anim3);
set.play(anim).with(anim2);
set.play(anim2).before(anim3);


set.start();


=======================================================================


【Fragment】


Fragment表现Activity中UI的一个行为或者一部分 可以把Fragment想象成Activity的一个模块化区域 有它自己的生命周期 接收属于自己监听事件 并且可以再Activity运行期间添加和删除


Frament必须嵌入到一个Activity中 它的生命周期直接受到其宿主Activity的生命周期影响 当一个Activity正在运行时 可以独立操作每个Fragment 比如添加或者删除


●生命周期
常用的几个生命周期函数:
onAttach(Context) –> 当fragment被绑定到activity上时回调


onCreate(Bundle) –> 当fragment对象创建的时候回调,一般在此方法里做参数接收


onCreateView(LayoutInflater, ViewGroup, Bundle) –> 创建fragment视图时回调


onResume –> 视图前台运行


onDestoryView –> 视图销毁时回调


onDestory –> 销毁fragment时回调


onDetech() –> fragment与activity失去关联时回调


●动态添加Fragment主要分为4步:
1.获取到FragmentManager 在V4包中通过getSupportFragmentManager 在系统中原生的Fragment是通过getFragmentManager获得的


2.开启一个事务 通过调用beginTransaction方法开启


3.向容器内加入Fragment 一般使用add或者replace方法实现 需要传入容器的id和Fragment的实例


4.提交事务 调用commit方法提交


【Fragment管理】


◆要管理activity中的fragments需要使用FragmentManager通过getSupportFragmentManager() 获得


常用的方法有:
manager.findFragmentById();  //根据ID来找到对应的Fragment实例 主要用在静态添加fragment的布局中 因为静态添加的fragment才会有ID  


manager.findFragmentByTag(); //根据TAG找到对应的Fragment实例 主要用于在动态添加的fragment中 根据TAG来找到fragment实例  


manager.getFragments(); //获取所有被ADD进Activity中的Fragment


◆一般用来对当前的Fragment进行管理 包括add,replace,remove;


常用的针对Fragment的方法有:


//将一个fragment实例添加到Activity的最上层  
add(int containerViewId, Fragment fragment, String tag);  


//将一个fragment实例从Activity的fragment队列中删除  
remove(Fragment fragment);  


//替换containerViewId中的fragment实例 注意 它首先把containerViewId中所有fragment删除 然后再add进去当前的fragment  


replace(int containerViewId, Fragment fragment);


◆调用commit方法提交事务后 想要进行别的操作 需重新开启一个新事务


【ViewPager】 一个可以左右滑动的控件 一般与Fragment一起使用


◆ViewPager与Fragment一起使用


Fragment fragment1 = new BlankFragment();
Fragment fragment2 = new Blank2Fragment();
Fragment fragment3 = new Blank3Fragment();
Fragment fragment4 = new Blank4Fragment();


list = new ArrayList<>();
list.add(fragment1);
list.add(fragment2);
list.add(fragment3);
list.add(fragment4);


MyAdapter adapter = new MyAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);


//适配器 与Fragment一起使用需继承FragmentPagerAdapter
class MyAdapter extends FragmentPagerAdapter{


    public MyAdapter(FragmentManager fm) {
         super(fm);
    }


    public Fragment getItem(int position) {
         return list.get(position);
    }


    public int getCount() {
         return list.size();
    }
}


◆ViewPager独自使用时


LayoutInflater inflater = getLayoutInflater();


View view = inflater.inflate(R.layout.fragment_blank,null);
View view2 = inflater.inflate(R.layout.fragment_blank_fragment2,null);
View view3 = inflater.inflate(R.layout.fragment_blank,null);
View view4 = inflater.inflate(R.layout.fragment_blank_fragment2,null);
View view5 = inflater.inflate(R.layout.fragment_blank,null);
list = new ArrayList<>();
list.add(view);
list.add(view2);
list.add(view3);
list.add(view4);
list.add(view5);


viewpager.setAdapter(new MyAdapter());


//单独使用时 继承PagerAdapter
class MyAdapter extends PagerAdapter{


    public int getCount() {
         return list.size();
    }


    public boolean isViewFromObject(View view, Object object) {
         return view==object;
    }


    public Object instantiateItem(ViewGroup container, int position) {
         container.addView(list.get(position));
         return list.get(position);
    }


    public void destroyItem(ViewGroup container, int position, Object object) {
         container.removeView(list.get(position));
    }
}


◆ViewPager监听


viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {


    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {


    }
    //与底部按钮进行一个绑定 类似于微信
    public void onPageSelected(int position) {
        switch (position){
            case 0:
                radioGroup.check(R.id.radio1);
                break;
            case 1:
                radioGroup.check(R.id.radio2);
                break;
            case 2:
                radioGroup.check(R.id.radio3);
                break;
            case 3:
                radioGroup.check(R.id.radio4);
                break;
         }


    }


    public void onPageScrollStateChanged(int state) {


    }
});


//按钮与页面也进行一个绑定
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {


    public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId){
            case R.id.radio1:
                viewPager.setCurrentItem(0);
                break;
            case R.id.radio2:
                viewPager.setCurrentItem(1);
                break;
            case R.id.radio3:
                viewPager.setCurrentItem(2);
                break;
            case R.id.radio4:
                viewPager.setCurrentItem(3);
                break;
        }
    }
});


=======================================================================


【Dialog】


Dialog是一个非常重要的UI, 它可以方便的给用户提示,用最简洁的方式向用户展示信息


其直接子类和间接子类有: AlertDialog  BottomSheetDialog  DatePickerDialog  TimePickerDialog  ProgressDialog  CharacterPickerDialog


【AlertDialog】


1.创建AlertDialog.Builder对象


2.调用Builder对象的setTitle()设置标题 setIcon设置图标


3.调用Builder对象的相关方法设置内容:
setMessage() : 设置简单文本框的内容
setItems() : 设置简单列表的内容,数组
setSingleChoiceItems() : 设置单选列表的内容 内容参数可以是数组Cursor ListAdapter
setMultiChoiceItems() :设置多选列表项的内容 内容参数可以是数组


4.调用Builder对象的setPositiveButton()和 setNegativeButton()设置按钮和监听器


5.调用Builder对象的create()方法创建AlertDialog对象 再调用AlertDialog对象的show()方法显示对话框


例如:


//初始化弹窗对象
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());


//设置标题图标 不设置标题 图标是不会显示的
builder.setIcon(R.mipmap.ic_launcher);
builder.setTitle("下载");


//设置文本框内容
builder.setMessage("是否下载?");


//确定按钮 有监听事件
builder.setPositiveButton("是",null);


//取消按钮 同样也有监听事件
builder.setNegativeButton("否", new DialogInterface.OnClickListener() {


    public void onClick(DialogInterface dialog, int which) {
        Toast.makeText(getContext(),"逗我呢",Toast.LENGTH_SHORT).show();
    }
});


//必须调用show()方法显示对话框
builder.show();


【DatePickerDialog和TimePickerDialog】


设置日历和时间


Calendar calendar = Calendar.getInstance();
new DatePickerDialog(MainActivity.this, new DatePickerDialog.OnDateSetListener() {


    public void onDateSet(DatePicker view, int year, int month, int day) {
        // TODO Auto-generated method stub
    }
}, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
        calendar.get(Calendar.DAY_OF_MONTH) ).show();




Calendar calendar = Calendar.getInstance();
new TimePickerDialog(MainActivity.this,
        new TimePickerDialog.OnTimeSetListener() {
            @Override
            public void onTimeSet(TimePicker view, int hour, int minute) {
                // TODO Auto-generated method stub


            }
        }, calendar.get(Calendar.HOUR_OF_DAY),
        calendar.get(Calendar.MINUTE), true).show();


【ProgressDialog】


//设置下载进度条弹窗
ProgressDialog dialog1 = new ProgressDialog(getContext());


//设置进度条为水平方向 ProgressDialog.STYLE_SPINNER设置进度条的形式为圆形
dialog1.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);


//设置点击返回键取消窗口显示
dialog1.setCancelable(true);


//设置点击弹窗外边界是否取消进度条窗口
dialog1.setCanceledOnTouchOutside(false);


//设置标题 默认没有
dialog1.setTitle("下载进度");


//设置进度条最大值
dialog1.setMax(100);


// 更新进度条的进度,可以在子线程中更新进度条进度                dialog.incrementProgressBy(1);


//更新进度条进度
dialog1.setProgress(i);


//关闭进度条对话框
dialog1.dismiss();


【自定义Dialog】


//自定义一个类继承Dialog 实现它的构造方法
//定义一个静态的内部类Builder


public static class Builder {
   private Context context;
   private String title, msg, ptext, ntext;
   private OnClickListener onClick, listener;


   public Builder(Context context) {
      this.context = context;
   }


   public Builder setTitle(String title) {
      this.title = title;
      return this;
   }


   public Builder setMessage(String msg) {
      this.msg = msg;
      return this;
   }


   public Builder setPositiveButton(String ptext, OnClickListener onClick) {
      this.ptext = ptext;
      this.onClick = onClick;
      return this;
   }


   public Builder setNegativeButton(String ntext, OnClickListener listener) {
      this.ntext = ntext;
      this.listener = listener;
      return this;
   }


   public MyDialog create() {
      //第二个参数传样式 可以传自定义的
      final MyDialog dialog = new MyDialog(context, R.style.Dialog);
      View view = LayoutInflater.from(context).inflate(R.layout.mydialog, null);
      TextView textView1 = (TextView) view.findViewById(R.id.title);
      TextView textView2 = (TextView) view.findViewById(R.id.message);
      Button button1 = (Button) view.findViewById(R.id.btn1);
      Button button2 = (Button) view.findViewById(R.id.btn2);


      if (title != null && !"".equals(title))
          textView1.setText(title);
      if (msg != null && !"".equals(msg))
          textView2.setText(msg);
      if (ptext != null && !"".equals(ptext))
          button1.setText(ptext);
      if (ntext != null && !"".equals(ntext))
          button2.setText(ntext);
      if (onClick != null)
          button1.setOnClickListener(new View.OnClickListener() {


             public void onClick(View v) {
                onClick.onClick(dialog, 0);
             }
          });
      if (listener != null)
          button2.setOnClickListener(new View.OnClickListener() {


             public void onClick(View v) {
                listener.onClick(dialog, 0);
             }
          });


      //一定要记得加载View
      dialog.setContentView(view);
      return dialog;
   }
}


//在页面上可以调用自定义的Dialog
new MyDialog.Builder(getContext())
     .setTitle("标题")
     .setMessage("请问你是好人吗?")
     .setPositiveButton("是的", new DialogInterface.OnClickListener() {


          public void onClick(DialogInterface dialog, int which) {
             Toast.makeText(getContext(), "", Toast.LENGTH_SHORT).show();
             dialog.dismiss();
          }
     })
     .setNegativeButton("不是", new DialogInterface.OnClickListener() {


          public void onClick(DialogInterface dialog, int which) {
             Toast.makeText(getContext(), "", Toast.LENGTH_SHORT).show();
          }
     })
     .create().show();


//在values里的style 需要配置 parent表示继承Dialog原有的所有属性
<style name="Dialog" parent="android:style/Theme.Dialog">
    <item name="android:background">#00000000</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowIsFloating">true</item>
</style>


【PopWindow】


//需要有自己的view 设置高宽 是否聚焦
PopupWindow pop =  new PopupWindow(view,ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT,true);


//设置窗口可点击 必须添加
pop.setTouchable(true);


//点back键和其他地方使其消失 设置了这个才能触发OnDismisslistener
ColorDrawable cd = new ColorDrawable(0000000000);
pop.setBackgroundDrawable(cd);


//设置在哪个控件下方显示 设置左右偏移量
pop.showAsDropDown(button,0,0);


//设置在底部显示 设置左右偏移量
pop.showAtLocation(button, Gravity.BOTTOM,0,0);


=======================================================================


【自定义控件】


1.定义一个类继承LinearLayout
2.实现前三个构造方法
3.在主程序的xml里调用自定义控件的xml


【例子】


//实现触摸、监听、输入框监听接口中的方法
public class MyLayout extends LinearLayout implements View.OnTouchListener, View.OnClickListener, TextWatcher {
    private Button button1, button2;
    private EditText edit;
    private int count = 1;
    private boolean isCancel = false;


    //将super改为this 后面传入一个null
    public MyLayout(Context context) {
        this(context, null);
    }


    public MyLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }


    public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        View view = View.inflate(context, R.layout.activity_main, this);
        button1 = (Button) view.findViewById(R.id.btn1);
        button2 = (Button) view.findViewById(R.id.btn2);
        edit = (EditText) view.findViewById(R.id.edit);


        //自定义控件设置颜色 在res/values文件下新建attrs文件
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyLayout);
        //第二个参数传默认值
        int bcolor1 = array.getColor(R.styleable.MyLayout_b_color1, getResources().getColor(R.color.colorAccent));
        int bcolor2 = array.getColor(R.styleable.MyLayout_b_color2, getResources().getColor(R.color.colorAccent));
        int ecolor = array.getColor(R.styleable.MyLayout_edit_color, getResources().getColor(R.color.colorAccent));
        //array要回收
        array.recycle();


        if (bcolor1 != 0)
            button1.setBackgroundColor(bcolor1);
        if (bcolor2 != 0)
            button2.setBackgroundColor(bcolor2);
        if (ecolor != 0)
            edit.setBackgroundColor(ecolor);


        //因为实现了接口中的方法 所以这里的监听事件直接传this
        edit.setText(count + "");
        button1.setOnClickListener(this);
        button2.setOnClickListener(this);
        edit.addTextChangedListener(this);
        button1.setOnTouchListener(this);
        button2.setOnTouchListener(this);
    }


    //触摸时 也就是长按时进入的方法
    public boolean onTouch(View v, MotionEvent event) {
        switch (v.getId()) {
            case R.id.btn1:
                //事件的action等于按下时
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    //设置它的标识符
                    isCancel = true;
                    new Thread(new Runnable() {
                        
                        public void run() {
                            while (isCancel) {
                                //用handler发送消息
                                handler.sendEmptyMessage(0);
                                try {
                                    Thread.sleep(200);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }).start();
                //事件的action等于松开时
                } else if (event.getAction() == MotionEvent.ACTION_UP) {
                    isCancel = false;
                //事件的action等于取消时
                } else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
                    isCancel = false;
                }
                break;
            case R.id.btn2:
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    isCancel = true;
                    new Thread(new Runnable() {
                        
                        public void run() {
                            while (isCancel) {
                                handler.sendEmptyMessage(1);
                                try {
                                    Thread.sleep(200);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }).start();
                } else if (event.getAction() == MotionEvent.ACTION_UP) {
                    isCancel = false;
                } else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
                    isCancel = false;
                }
                break;
        }
        //true为拦截 false为不拦截
        return true;
    }


    Handler handler = new Handler() {
        
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int num = Integer.parseInt(edit.getText().toString());
            if (msg.what == 0) {
                num--;
                edit.setText(num + "");
                //设置输入框的光标在数字后面
                edit.setSelection(edit.getText().length());
            } else if (msg.what == 1) {
                num++;
                edit.setText(num + "");
                edit.setSelection(edit.getText().length());
            }
        }
    };


    //单击按钮时
    public void onClick(View v) {
        int num = Integer.parseInt(edit.getText().toString());
        switch (v.getId()) {
            case R.id.btn1:
                if (num > 0 && num < 100) {
                    num--;
                    edit.setText(num + "");
                    edit.setSelection(edit.getText().length());
                }
                break;
            case R.id.btn2:
                if (num > 0 && num < 100) {
                    num++;
                    edit.setText(num + "");
                    edit.setSelection(edit.getText().length());
                }
                break;
        }
    }


    //对输入框的一个监听
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {


    }


    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //输入框删除数据为空时 设置值为1 不能改的
        if ("".equals(s.toString())) {
            edit.setText("1");
            edit.setSelection(edit.getText().length());
        } else {
            int num = Integer.parseInt(s.toString());
            //当减到0时也会变为1
            if (num <= 0) {
                edit.setText("1");
                edit.setSelection(edit.getText().length());
            //当加到100时设为99  相当于设置了一个最小值和一个最大值
            } else if (num >= 100) {
                edit.setText("99");
                edit.setSelection(edit.getText().length());
            }
        }
    }


    @Override
    public void afterTextChanged(Editable s) {


    }
}


//模拟一个加载的界面
public class Mylayout2 extends LinearLayout {
    private LinearLayout root,load,flush;
    private ImageView img;
    private Button btn;
    public Mylayout2(Context context) {
        this(context,null);
    }


    public Mylayout2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }


    public Mylayout2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        View view = View.inflate(context,R.layout.load,this);
        root = (LinearLayout) view.findViewById(R.id.root);
        load = (LinearLayout) view.findViewById(R.id.load);
        flush = (LinearLayout) view.findViewById(R.id.flush);
        img = (ImageView) view.findViewById(R.id.img);
        btn = (Button) view.findViewById(R.id.btn);


        //加载一个帧动画
        AnimationDrawable drawable = (AnimationDrawable) img.getBackground();
        drawable.start();
    }


    //加载时 调整布局的显示和隐藏 GONE隐藏 VISIBLE显示
    public void load(){
        flush.setVisibility(GONE);
        load.setVisibility(VISIBLE);
    }
    //完成时 整个大的布局全部隐藏
    public void finish(){
        root.setVisibility(GONE);
    }
    public void flush(final Activity activity){
        flush.setVisibility(VISIBLE);
        load.setVisibility(GONE);
        flush.setOnClickListener(new OnClickListener() {
            
            public void onClick(View v) {
                //重新加载当前activity
                activity.recreate();
            }
        });
    }
}


//Activity
private Mylayout2 layout;
layout = (Mylayout2) findViewById(R.id.layout);


//直接调自定义控件中的加载方法
layout.load();
new Thread(new Runnable() {
    
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //休眠2秒后 调用完成的方法
        runOnUiThread(new Runnable() {
        
            public void run() {
                layout.finish();
            }
        });
    }
}).start();


◆在主程序的xml里运用自定义控件的xml


<com.example.administrator.myapplication.Mylayout2 xmlns:android="http://schemas.android.com/apk/res/android"
    //自定义一个命名空间 在res/values中的attrs文件下配置
    xmlns:ll = "http://schemas.android.com/apk/res-auto"
    //设置一个id
    android:id="@+id/layout"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center|top">


    //包名加上项目名称 就可以直接调用
    <com.example.administrator.myapplication.MyLayout
        ll:b_color1="@android:color/holo_blue_light"
        ll:b_color2="@android:color/holo_green_light"
        ll:edit_color="@android:color/holo_orange_light"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">


    </com.example.administrator.myapplication.MyLayout>
</com.example.administrator.myapplication.Mylayout2>


◆自定义控件Mylayout2中的xml文件


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <LinearLayout
        android:id="@+id/load"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">


        <ImageView
            android:id="@+id/img"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/frame"/>


    </LinearLayout>


    <LinearLayout
        android:id="@+id/flush"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">


        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />


    </LinearLayout>
</LinearLayout>


◆res/values下的attrs文件


<resources>
    //名字一定要写需要用的自定义控件类名
    <declare-styleable name="MyLayout">
        //名字自定义 format为格式 可以写成资源文件reference或者对应的格式
        <attr name="b_color1" format="color"/>
        <attr name="b_color2" format="color"/>
        <attr name="edit_color" format="color"/>
    </declare-styleable>
</resources>


=======================================================================


【画图】


1.在drawable里建立xml文件
2.layer-list表示一个集合
3.shape可以画形状
4.几个基础属性:
  ◆corners  角度
  ◆gradient 渐变
  ◆padding  间隔 一般直接在布局里去设置
  ◆solid    填充
  ◆stroke   描边
5.item下设置的距离表示距离底层多少边距(画在后面的会覆盖先前的)
6.可以完美避免边框不缝合


例子:画在先前的类似与后面的边框


<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <corners
                android:topLeftRadius="20dp"
                android:bottomLeftRadius="20dp"/>
            <solid
                android:color="@android:color/white"/>
        </shape>
    </item>
    <item android:top="1dp" android:left="1dp" android:bottom="1dp">
        <shape>
            <corners
                android:topLeftRadius="20dp"
                android:bottomLeftRadius="20dp"/>
            <solid
                android:color="@android:color/holo_blue_light"/>
        </shape>
    </item>
</layer-list>


========================================================================
【APP优化】


◆include标签复用控件 xml里只有一个layout属性
◆merge标签减少布局嵌套
◆viewStub标签在首次运行不需要控件布局 需在代码中动态注册显示在界面上 减少内存占用
◆点9图 一般图很小 不会占太多内存 图片不会因为缩放而变形
  上边、左边为缩放点 下边、右边为内容显示区域 为蓝色
  (SDK\tools draw9patch.bat可以制作点9图)


1.代码优化
 ①尽量少用全局变量 使用局部变量 可以减少内存的占用
 ②尽量用final关键字修饰
 ③逻辑理清楚 减少不必要的代码
 ④能使用标准算数函数库的 不要自己写方法
 ⑤少调用JNI 会非常占用内存
 ⑥字符串拼接多使用StringBuffer代替“+”
2.布局优化
 ①多使用include标签和viewStub、megre
 ②使用相对布局减少层级嵌套
 ③减少inflate的使用
3.图片优化
 ①使用.9图
4.控件优化
 ①listview使用ViewHolder 同时少进行耗时操作
 ②使用RecycleView代替ListView
5.内存优化
 ①及时关闭Handler等资源对象 防止内存泄露


【ScrollView】 只能有一个布局或者一个控件 所以需要用到嵌套


1、ScrollView和HorizontalScrollView是为控件或者布局添加滚动条


2、上述两个控件只能有一个孩子 但是它并不是传统意义上的容器


3、上述两个控件可以互相嵌套


4、滚动条的位置现在的实验结果是:可以由layout_width和layout_height设定


5、ScrollView用于设置垂直滚动条 HorizontalScrollView用于设置水平滚动条:需要注意的是,有一个属性是scrollbars可以设置滚动条的方向 但是ScrollView设置成horizontal是和设置成none是效果同 HorizontalScrollView设置成vertical和none的效果同


6、ScrollView与ListView一起有会导致ListView显示不全
优化:自定义ListView 实现onMeasure方法


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


  /**
   * listView与scrollView一起用会导致listView只显示出一行 所以需要进行优化
   * Integer.MAX_VALUE >> 2 右移两位 是listView高度的最大值
   * MeasureSpec.AT_MOST 模式为有多少内容取多少高度
   *
   * */


   int heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
   super.onMeasure(widthMeasureSpec, heightSpec);
}


◆imageView重写 设置图片的宽高


public MyHomeImg(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);


    //在res/values下attrs文件夹设置的属性 传一个默认值
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyHomeImg);
    myWidth = array.getInteger(R.styleable.MyHomeImg_myWidth, 2);
    myHeight = array.getInteger(R.styleable.MyHomeImg_myHeight, 1);
    array.recycle(); //回收array


}


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //获取屏幕的宽度 在xml里imageView设置的是match_parent
    int width = MeasureSpec.getSize(widthMeasureSpec);
    //获取图片的高度 一般宽高比为2:1
    int height = width * myHeight / myWidth;
    //设置图片的大小
    setMeasuredDimension(width, height);
}


◆res/values下attrs文件


<resources>
    <declare-styleable name="MyHomeImg">
        <attr name="myWidth" format="integer" />
        <attr name="myHeight" format="integer" />
    </declare-styleable>
</resources>


【WebView】


重写WebView 加载页面html5


public class MyWebView extends WebView implements View.OnLongClickListener{


    public MyWebView(Context context) {
        this(context,null);
        this.setOnLongClickListener(this);
    }


    public MyWebView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
        this.setOnLongClickListener(this);
    }


    public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setOnLongClickListener(this);
    }


    //实现了接口中的方法
    @Override
    public boolean onLongClick(View v) {
        return true;
    }
}


◆Html5Activity


//设置webView的属性
WebSettings settings = myWebView.getSettings();
// 将图片调整到适合webview的大小
settings.setUseWideViewPort(false);
// 支持js
settings.setJavaScriptEnabled(true);
// 支持自动加载图片
settings.setLoadsImagesAutomatically(true);


//设置一个webView的客户端 实现三个方法
myWebView.setWebViewClient(new WebViewClient(){


    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        //页面中自己对应的URL数据
        view.loadUrl(url);
        return super.shouldOverrideUrlLoading(view, url);
    }


    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        loadPage.finish();
    }


    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        super.onReceivedError(view, request, error);
        loadPage.error(Html5Activity.this);
    }
});


========================================================================


【事件分发】


dispatchTouchEvent是处理触摸事件分发 事件(多数情况)是从Activity的dispatchTouchEvent开始的 执行super.dispatchTouchEvent(ev)事件向下分发


onInterceptTouchEvent是ViewGroup提供的方法 默认返回false 返回true表示拦截


onTouchEvent是View中提供的方法 ViewGroup也有这个方法 view中不提供onInterceptTouchEvent view中默认返回true表示消费了这个事件


★Android中默认情况下事件传递是由最终的view的接收到 传递过程是从父布局到子布局 也就是从Activity到ViewGroup到View的过程 默认情况ViewGroup起到的是透传作用


◆View里,有两个回调函数 :
  public boolean dispatchTouchEvent(MotionEvent ev);    
  public boolean onTouchEvent(MotionEvent ev);


◆ViewGroup里,有三个回调函数 :
  public boolean dispatchTouchEvent(MotionEvent ev);    
  public boolean onInterceptTouchEvent(MotionEvent ev);    
  public boolean onTouchEvent(MotionEvent ev);  


◆在Activity里,有两个回调函数 :
  public boolean dispatchTouchEvent(MotionEvent ev);    
  public boolean onTouchEvent(MotionEvent ev);


========================================================================


【MVP】


Model-view-presenter(MVP)是使用者接口设计模式的一种被广范用于便捷自动化单元测试和在呈现逻辑中改良分离关注点


◆Model 定义使用者接口所需要被显示的资料模型 一个模型包含着相关的商业逻辑




◆View 视图为呈现使用者接口的终端 用以表现来自 Model 的资料 和使用者命令路由再经过 Presenter 对事件处理后的资料


◆Presenter 包含着元件的事件处理 负责检索 Model 取得资料 和将取得的资料经过格式转换与 View 进行沟通


========================================================================


◆自定义控件 先进入onMeasure方法 此方法可以设置控件的高宽 然后进入onDraw方法


//类似与画板
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);


    //相当于画笔
    Paint p = new Paint();
    //设置颜色
    p.setColor(Color.DKGRAY);
    //设置抗锯齿
    p.setAntiAlias(true);
    //可以画圆、直线、矩形等等
    canvas.drawCircle(400, 500, size * f, p);
    p.setColor(Color.BLACK);
    canvas.drawCircle(400, 500, size / 2 * f, p);
    p.setColor(Color.RED);
    canvas.drawCircle(400, 500, size / 3 * f, p);
}


=========================================================================


【android 屏幕适配】


1、图片适配   根据不同分辨率 切多套图片 放到相应的图片文件夹下面
2、权重适配
3、根据要适配的分辨率 新建对应的values文件夹 把XML中的dp和sp写到不同dimens下 根据不同分辨率修改对应的dp和sp的大小 单位使用dp和sp不要使用px
4、使用相对布局 不要使用绝对布局
5、图片使用点9图
6、在代码中根据适配的比例动态修改控件大小
7、使用谷歌官方出的百分比布局


mdpi   120dpi~160dpi
hdpi   160dpi~240dpi
xhdpi   240dpi~320dpi
xxhdpi   320dpi~480dpi
xxxhdpi   480dpi~640dpi


=========================================================================


【Bitmap】 加载本地资源大图


用Bitmap工厂类实例化bitmapFactory 在类中设置相对应的参数 图片解码转换为2字节


BitmapFactory.Options工厂类 设置颜色模式 RGB_565为2字节 ARGB_8888为4字节 减少内存占用


加载资源文件的大图用drawable防止内存溢出 可将bitmap转换为drawable drawable不可以设置缩放 bitmap可以


bitmap加载图片可能会造成内存溢出 不及时回收可能会造成内存泄露


【数据库的操作】


在res资源文件下有一个.db的数据文件 


1.从res文件下读取数据库文件 转换为流的形式 保存到本地
2.使用SQLiteDataBase操作数据库


原有的数据不改变 修改表结构


1.首先备份数据
2.删除原有的表结构 新建一个数据库
3.导入备份的数据


【线程池】 方便管理线程 节约资源 可同时进行多个线程


//创建一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();


//异步任务 传对象过去则能判断是哪一个对象
MyAsyncTask task = new MyAsyncTask((ProgressBar) v);
task.executeOnExecutor(executorService);


//直接new线程 无需handler发送消息进行更新
executorService.execute(new Runnable() {
   @Override
   public void run() {
      int count = 0;
      while (count <100){
         count++;
         try {
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         viewHolder.progressBar.setProgress(count);
      }
   }
});


【图片的处理】(或者图片三级缓存)


图片三级缓存:内存缓存,本地缓存,网络缓存


图片处理:imageview加载图片,首先查看内存是否存在图片缓存,如果没有查看本地SD卡里是否存在图片缓存,如果有更新内存缓存,如果没有则访问网络,开一个线程池,从网络加载图片,更新到内存缓存,下次加载则可以直接在内存缓存中加载图片


LruCache来进行内存的缓存


【第三方缓存和离线加载图片】


ACache ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)。
1、它可以缓存什么东西?


普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 byte数据。


2、它有什么特色?


特色主要是: 
1:轻,轻到只有一个JAVA文件。 
2:可配置,可以配置缓存路径,缓存大小,缓存数量等。 
3:可以设置缓存超时时间,缓存超时自动失效,并被删除。 
4:支持多进程。


ACache mCache = ACache.get(this);
mCache.put("test_key1", "test value");
mCache.put("test_key2", "test value", 10);//保存10秒,如果超过10秒去获取这个key,将为null
mCache.put("test_key3", "test value", 2 * ACache.TIME_DAY);//保存两天,如果超过两天去获取这个key,将为null


获取数据
ACache mCache = ACache.get(this);
String value = mCache.getAsString("test_key1");


Glide 用于加载图片 离线加载也是可以的 只要url不为null 
用法:
Glide.with(当前Acitivity).load(图片url).into(ImageView);


需添加Glide  jar包


Android ListView 内容随着键盘弹出而向上推  
xml文件加入这条属性就ok了
 android:transcriptMode="alwaysScroll" 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值