智慧北京模块讲解(框架搭建,工作中的重要技巧!)
演示ViewPager的预加载功能
Android ViewPager 的预加载
android新引入的Fragment加入了Viewpager这样的一个组件。至于该控件的功能在这里就不说了。这篇文章主要解决fragmentviewpager预加载的问题。默认的viewpager是android 新引入的Fragment加入了Viewpager这样的一个组件。至于该控件的功能在这里就不说了。这篇文章主要解决fragment viewpager预加载的问题。默认的viewpager是会预先加载下一个fragment的控件的,可以通过setOffscreenPageLimit(int limit) 来设置要提前加载的fragment。即是说当limit等于5的时候,打开第一个fragment的时候就相当于同时打开了其他的4个fragment了。这样切换的时候将会非常流畅。但是,当每个fragment都需要去联网加载网络数据或者做一些耗时的操作,而且有其他的fragment并不是必须的,用户不一定会打开。这样的话如果还预加载的话将会浪费资源,用户体验也不好。虽然可以通过setOffscreenPageLimit(0)来设置不提前预加载,但这样的设置没有效果。通过研究ViewPager的源码,可以设置该类默认的DEFAULT_OFFSCREEN_PAGES = 0,来预防预加载。
*ViewPager默认加载第0页,而真正预加载第1页
之前的Guide项目继续演示Viewpager的预加载功能:
GuideActivity.java
//给viewpager添加一个view的方法
@Override
public Object instantiateItem(ViewGroup container, int position) {
//container就是viewpager对象
Log.i(tag, "instantiateItem position = "+position);
container.addView(imageList.get(position));
return imageList.get(position);
}
//给viewpager移除view的方法
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.i(tag, "destroyItem position = "+position);
container.removeView((View)object);
}
重点!!(安卓中控件没有宽高-->宽高设置)用案例说明之前的用java代码给布局做手机适配
之前案例:用java代码给布局做手机适配,问题是,为什么要先拿到RelativeLayout的宽高后再设置到TextView控件上呢?为什么?
public class MainActivity extends Activity {
private static final String tag = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.tv);
//获取屏幕宽高
DisplayMetrics outMetrics = new DisplayMetrics();
//必须调用此api才可获取宽高值
getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
Log.i(tag,"outMetrics.heightPixels = "+outMetrics.heightPixels);
Log.i(tag,"outMetrics.widthPixels = "+outMetrics.widthPixels);
//先将当前的宽高设置给textview所在父布局中,作用在textView上。
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
//数学四舍五入
//320px (int)(160px+0.5) 160px
//321px 160.5px 接近161px (int)(160.5px+0.5px) 161px
//321.2px 160.6px 161px (int)(161.1px) 161px
(int)(outMetrics.widthPixels*0.5+0.5),
(int)(outMetrics.heightPixels*0.5+0.5));
textView.setLayoutParams(layoutParams);
}
}
演示项目Button_layout来解释上面的问题
自定义一个button_item.xml,在里面的button控件设置的是宽高都300dp
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="300dp"
android:layout_height="300dp"
android:text="@string/hello_world" />
<!-- android中控件是没有宽高,父布局 -->
在MainActivity里用代码去让activitymain.xml的对象去使用addView去把buttonitem.xml的对象加到activity_main.xml布局中,把其跑到模拟器中发现其button控件设置的是宽高300dp都失效了。
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/rl"
tools:context=".MainActivity" >
</RelativeLayout>
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout layout = (RelativeLayout) findViewById(R.id.rl);
View view = View.inflate(getApplicationContext(), R.layout.button_item, null);
layout.addView(view);
}
}
原因:那是因为控件本身(除非自己是父布局)是没有宽高的,是通过父布局把宽高给控件。
安卓sdk-->tools--->Hierarchy Viewer工具里有显示说明
还有把之前的adapter_java里的RelativeLayout.LayoutParams改成LinearLayout.LayoutParams这样不可以,因为控件的父布局不匹配。
演示Json
工程zhbj+左滑侧拉栏的开源项目+XUtils
让zhbj(智慧北京)项目去关联这两个library
该zhbj项目需要配合tomcat服务器
项目是UTF-8的编码,我们变成需要注意,以后也一致使用UTF-8
新建一个Demo,把Tomcat服务端里的zhbj资源(Json资源)放入到Demo里的assert目录下,ctrl+H去搜索资源里的原来的IP后修改成现在的IP,修改完毕后复制回到Tomcat
用浏览器去浏览服务器中的json文件:
思考客户端zhbj怎么和服务端去做交互?
让zhbj(智慧北京)工程访问该服务器的json,访问前注意修改访问路径
老师资料里有Json的格式化工具HiJson
讲解json和zhbj之间的数据交互细节 .
公司的接口文档:是用来告诉员工如何设计json和服务器做交互
分析智慧北京主页面的布局及使用到的技术,并使用技术2来演示实现智慧北京的主页面
继续在昨天的引导Demo项目:Guide(我们将使用Fragment+RadioGroup技术设计该主页)
MainActivity实现智慧北京主页面,把frag_home.xml设置其背景布局资源
MainActivity.java public class MainActivity extends FragmentActivity { private FrameLayout layout_content; private RadioGroup main_radio; private int index; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.frag_home); frag_home.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <FrameLayout android:id="@+id/layout_content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1.0" /> <!-- <android.support.v4.view.ViewPager --> <!-- android:id="@+id/layout_content" --> <!-- android:layout_width="match_parent" --> <!-- android:layout_height="0dip" --> <!-- android:layout_weight="1.0" > --> <!-- </android.support.v4.view.ViewPager> --> <RadioGroup android:id="@+id/main_radio" android:layout_width="fill_parent" android:layout_height="60dp" android:layout_gravity="bottom" android:background="@drawable/bottom_tab_bg" android:gravity="center_vertical" android:orientation="horizontal" android:paddingTop="2dp" > <RadioButton android:id="@+id/rb_function" style="@style/main_tab_bottom" android:drawableTop="@drawable/icon_function" android:text="@string/tab_function" /> <RadioButton android:id="@+id/rb_news_center" style="@style/main_tab_bottom" android:drawableTop="@drawable/icon_newscenter" android:text="@string/tab_news_center" /> <RadioButton android:id="@+id/rb_smart_service" style="@style/main_tab_bottom" android:drawableTop="@drawable/icon_smartservice" android:text="@string/tab_smart_service" /> <RadioButton android:id="@+id/rb_gov_affairs" style="@style/main_tab_bottom" android:drawableTop="@drawable/icon_govaffairs" android:text="@string/tab_gov_affairs" /> <RadioButton android:id="@+id/rb_setting" style="@style/main_tab_bottom" android:drawableTop="@drawable/icon_setting" android:text="@string/tab_setting" /> </RadioGroup> </LinearLayout> strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">引导界面</string> <string name="action_settings">Settings</string> <string name="hello_world">Hello world!</string> <string name="tab_function">首页</string> <string name="tab_news_center">新闻中心</string> <string name="tab_smart_service">智慧服务</string> <string name="tab_gov_affairs">政务指南</string> <string name="tab_setting">设置</string> </resources>
frag_home.xml使用RadioGroup
1、在MainActivity获取到布局中的RadioGroup的对象
layout_content = (FrameLayout) findViewById(R.id.layout_content);
main_radio = (RadioGroup) findViewById(R.id.main_radio);
2、设置选中RadioGroup中按钮状态发生改变的时候的事件监听(当前选中按钮变化的时候调用的方法)
//选中按钮状态发生改变的时候的事件监听
main_radio.setOnCheckedChangeListener(new OnCheckedChangeListener() {
//当前选中按钮变化的时候调用的方法
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.rb_function:
index = 0;
break;
case R.id.rb_news_center:
index = 1;
break;
case R.id.rb_smart_service:
index = 2;
break;
case R.id.rb_gov_affairs:
index = 3;
break;
case R.id.rb_setting:
index = 4;
break;
}
3、接着就让fragment的数据适配器(new出来)去让fragment填充每个按钮状态的指向(把MainActivity继承换成Fragment)
FragmentStatePagerAdapter fragmentStatePagerAdapter =
new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public int getCount() {
return null;
}
//返回根据索引创建的fragment对象
@Override
public Fragment getItem(int arg0) {
return null;
}
};
4、实现适配器中的方法getCount和getItem的方法
FragmentStatePagerAdapter fragmentStatePagerAdapter =
new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public int getCount() {
return 5;
}
//返回根据索引创建的fragment对象
@Override
public Fragment getItem(int arg0) {
BaseFragment baseFragment = null;
switch (arg0) {
case 0:
baseFragment = new FunctionFragment();
break;
case 1:
baseFragment = new NewCenterFragment();
break;
case 2:
baseFragment = new SmartServiceFragment();
break;
case 3:
baseFragment = new GovAffairsFragment();
break;
case 4:
baseFragment = new SettingFragment();
break;
}
return baseFragment;
}
};
5、在getItem里进行fragment的切换逻辑设计,并创建BaseFragment接口
6、(重要)技巧:为了让以后的程序员在继承BaseFragment时,不是让BaseFragment去实现UI设计的逻辑,而是让继承的Fragment去实现UI设计的逻辑,所以就把BaseFragment设计成一个接口类,并提供接口给继承它的Fragment使用 ,里面还有一个加载数据的方法
BaseFragment.java
public abstract class BaseFragment extends Fragment {
private View view;
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
//加载UI方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = initView();
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
initData();
super.onActivityCreated(savedInstanceState);
}
//加载数据方法
public abstract void initData();
//填充UI方法
public abstract View initView();
7、回到MainActivity里的getItem方法,并实现第一个Fragment:FunctionFragment,让FunctionFragment去实现设计UI的方法和填充数据的方法(initData+initUI),接着陆续实现其他的功能Fragment(新闻中心,智慧服务,政务指南,设置)
FunctionFragment.java
public class FunctionFragment extends BaseFragment {
@Override
public void initData() {
}
@Override
public View initView() {
TextView textView = new TextView(getActivity());
textView.setText("首页");
return textView;
}
}
8、回到第2步,在该方法里是包括了第5步里的getItem方法,并做Fragment替换操作,并提交事务(更新)
//fragment需要去替换帧布局内部内容的对象,获取fragment的索引,也就是说根据case所获取的index去拿到适配器里的某一个Fragment
Fragment fragment = (Fragment) fragmentStatePagerAdapter.instantiateItem(layout_content,index);
//1,替换的帧布局对象,3,索引生成fragment对象
fragmentStatePagerAdapter.setPrimaryItem(layout_content, 0, fragment);
//提交
fragmentStatePagerAdapter.finishUpdate(layout_content);
9、设置打开app的按钮的默认选中:
//默认选中按钮中按钮的方法
main_radio.check(R.id.rb_function);
框架完成,测试
bug:fragment覆盖重叠了
需求:我Fragment显示,你Fragment隐藏
10、在BaseFragment里实现SetMenuVisibility方法,控制界面fragment的显示隐藏,如果当前界面显示true,不显示false
//控制界面的显示隐藏,如果当前界面显示true,不显示false
@Override
public void setMenuVisibility(boolean menuVisible) {
//获取当前子类Fragment对应的view对象
if(getView()!=null){
getView().setVisibility(menuVisible?View.VISIBLE:View.GONE);
}
super.setMenuVisibility(menuVisible);
}
测试:框架完成。
智慧北京主页面的布局的第三种实现方式ViewPager+RadioGroup
考虑到左侧侧拉栏由于每个功能模块都不一样。我们就把其左侧侧拉去替换Fragment,减少耦合
项目zhbj_heima47演示(该项目是最终智慧北京的雏形)
把以前的项目中的res内容复制过来
把左侧拉栏的library引入进来
1、在MainActivity的继承改成SlidingFragmentActivity
public class MainActivity extends SlidingFragmentActivity {
2、设置内容显示布局
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.content);
3、设置左侧侧拉栏的布局menu_frame
setBehindContentView(R.layout.menu_frame);
4、设置左侧侧拉栏样式
SlidingMenu slidingMenu = getSlidingMenu();
slidingMenu.setBehindWidthRes(R.dimen.slidingmenu_offset);
slidingMenu.setShadowDrawable(R.drawable.shadow);
slidingMenu.setShadowWidthRes(R.dimen.shadow_width);
slidingMenu.setMode(SlidingMenu.LEFT);
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
5、让左侧侧拉栏替换成MenuFragment:给Fragment设置BaseFragment(加载UI方法接口+加载数据填充UI的方法接口)
BaseFragment.java
public abstract class BaseFragment extends Fragment {
public View view;
public Context context;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//考虑到继承该BaseFragment的Fragment会使用到上下文对象。所以在这里提前为子类准备好
context = getActivity();
}
//加载UI 方法,xml---->view
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = initView();
return view;
}
//加载数据,填充UI的方法
@Override
public void onActivityCreated(Bundle savedInstanceState) {
initData();
super.onActivityCreated(savedInstanceState);
}
public abstract View initView() ;
public abstract void initData() ;
}
6、MenuFragment继承BaseFragment,给其设计一个TextView
public class MenuFragment extends BaseFragment {
private static final String tag = "MenuFragment";
private List<String> titleList = new ArrayList<String>();
@Override
public View initView() {
TextView textView = new TextView(context);
textView.setText("左侧侧拉栏目");
return textView;
}
@Override
public void initData() {
}
}
7、回到第5步,继续让左侧侧拉的对象替换该MenuFragment
//fragment替换左侧侧拉栏目
MenuFragment menuFragment = new MenuFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.menu, menuFragment, "MENU")
.commit();
框架搭建完毕,测试
在项目zhbj_heima47中实现新创建的HomeFragment的内容添加进去
顶部是ViewPager,底部是RadioGroup
实现步骤(搭建框架)
HomeFragment
1、在initView里填充frag_home
@Override
public View initView() {
view = View.inflate(context, R.layout.frag_home, null);
return view;
}
frag_home.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<!-- <FrameLayout -->
<!-- android:id="@+id/layout_content" -->
<!-- android:layout_width="match_parent" -->
<!-- android:layout_height="0dip" -->
<!-- android:layout_weight="1.0" -->
<!-- /> -->
<view
android:id="@+id/layout_content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1.0"
class="com.example.zhbj_heima47.view.MyViewPager"/>
<!-- <android.support.v4.view.ViewPager
android:id="@+id/layout_content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1.0" /> -->
<RadioGroup
android:id="@+id/main_radio"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:background="@drawable/bottom_tab_bg"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="2dp" >
<RadioButton
android:id="@+id/rb_function"
style="@style/main_tab_bottom"
android:drawableTop="@drawable/icon_function"
android:text="@string/tab_function" />
<RadioButton
android:id="@+id/rb_news_center"
style="@style/main_tab_bottom"
android:drawableTop="@drawable/icon_newscenter"
android:text="@string/tab_news_center" />
<RadioButton
android:id="@+id/rb_smart_service"
style="@style/main_tab_bottom"
android:drawableTop="@drawable/icon_smartservice"
android:text="@string/tab_smart_service" />
<RadioButton
android:id="@+id/rb_gov_affairs"
style="@style/main_tab_bottom"
android:drawableTop="@drawable/icon_govaffairs"
android:text="@string/tab_gov_affairs" />
<RadioButton
android:id="@+id/rb_setting"
style="@style/main_tab_bottom"
android:drawableTop="@drawable/icon_setting"
android:text="@string/tab_setting" />
</RadioGroup>
</LinearLayout>
2、通过XUtil去把ViewPager对象和RadioGroup对象关联进来
@ViewInject(R.id.layout_content)
private MyViewPager layout_content;
@ViewInject(R.id.main_radio)
private RadioGroup main_radio;
@Override
public View initView() {
view = View.inflate(context, R.layout.frag_home, null);
//xutil,注解的方式找到控件
ViewUtils.inject(this, view);
return view;
}
自定义控件:
MyViewPager.java
/**
* 禁止viewpager滑动
*
* @author Administrator
*
*/
//LazyViewPager屏蔽ViewPager预加载操作的类
public class MyViewPager extends LazyViewPager {
private boolean HAS_TOUCH_MODE = false;
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyViewPager(Context context) {
super(context);
}
//向内部控件传递
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
//不响应事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
}
3、实现initData方法,给ViewPager去填充内容,MyPagerAdapter方法
4、initData方法实现每个按钮点击要切换的Pager,让ArrayList存储BasePager的实现类,设计BasePager
@Override
public void initData() {
pagerList = new ArrayList<BasePager>();
5、BasePager,给继承类设计初始化UI接口,初始化数据接口、还有构造方法里把上下文对象传进来。在构造方法里调用初始化UI接口并把View对象返回给BasePager里的View对象,并设计getRootView方法返回继承者实现UI后的view对象
public abstract class BasePager {
public Context context;
private View view;
//构建UI的方法
public abstract View initView();
//填充数据的方法
public abstract void initData();
public BasePager(Context context) {
this.context = context;
//当前的view其实就是页面的展示效果
view = initView();
}
//返回当前页面样式的方法
public View getRootView(){
return view;
}
6、回到HomeFragment里继续实现initData方法,给ArrayList添加ViewPager对象,添加同时把ViewPager对象都创建出来(功能页,新闻中心,智慧服务,政务指南,设置等)FunctionPager/NewCenterPager/SmartServicePager/GovAffairPager/SettingPager
@Override
public void initData() {
pagerList = new ArrayList<BasePager>();
pagerList.add(new FunctionPager(context));
pagerList.add(new NewCenterPager(context));
pagerList.add(new SmartServicePager(context));
pagerList.add(new GovAffarisPager(context));
pagerList.add(new SettingPager(context));
FunctionPager.java
public class FunctionPager extends BasePager {
public FunctionPager(Context context) {
super(context);
}
@Override
public View initView() {
TextView textView = new TextView(context);
textView.setText("首页");
return textView;
}
@Override
public void initData() {
// TODO Auto-generated method stub
}
}
目前的框架搭建好了,每个Pager页面之间都是独立的,5个模块,可以分发给5个人去干了,该框架的耦合性为0.
7、继续实现MyPagerAdapter里的逻辑方法
class MyPagerAdatper extends PagerAdapter{
@Override
public int getCount() {
return pagerList.size();
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.i(tag, "instantiateItem position = "+position);
container.addView(pagerList.get(position).getRootView());
return pagerList.get(position).getRootView();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.i(tag, "destroyItem position = "+position);
container.removeView((View)object);
}
8、在iniData方法去给layout_content.setAdapter(viewpager的适配器设置)
layout_content.setAdapter(new MyPagerAdatper());
9、实现点击底部按钮实现Viewpager的切换,在HomeFragment的initData方法里实现main_radio的点击事件监听
main_radio.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.rb_function:
break;
case R.id.rb_news_center:
break;
case R.id.rb_smart_service:
break;
case R.id.rb_gov_affairs:
break;
case R.id.rb_setting:
break;
}
}
});
10、在点击里指挥viewpager对象指向哪个VIewPager对象
main_radio.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.rb_function:
layout_content.setCurrentItem(0);
break;
case R.id.rb_news_center:
layout_content.setCurrentItem(1);
break;
case R.id.rb_smart_service:
layout_content.setCurrentItem(2);
break;
case R.id.rb_gov_affairs:
layout_content.setCurrentItem(3);
break;
case R.id.rb_setting:
layout_content.setCurrentItem(4);
break;
}
}
});
11、同时main_radio设置一个默认的选中
//默认选中首页
main_radio.check(R.id.rb_function);
12、最后在MainActivity里把内容页的布局替换成该HomeFragment的Fragment布局
HomeFragment homeFragment = new HomeFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, homeFragment, "HOME")
.commit();
}
测试:大致功能搞定,但是ViewPager的预加载问题(对用户体验没什么差异,主要是为了优化项目),还有手动滑动界面切换时,ViewPager不能去滑动。修改这些bug
问题解决1:ViewPager的预加载问题:
在自定义控件MyViewPager的父亲类LazyViewPager.java里解决,把DefaultOffscreenPagerLimit该成0
问题解决2:手动滑动界面切换时,ViewPager不能去滑动:
在MyViewPager进行如下设置就oK
/**
* 禁止viewpager滑动
*
* @author Administrator
*
*/
//LazyViewPager屏蔽ViewPager预加载操作的类
public class MyViewPager extends LazyViewPager {
private boolean HAS_TOUCH_MODE = false;
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyViewPager(Context context) {
super(context);
}
//向内部控件传递
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
//不响应事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
}
演示图片和按钮控件的事件的点击响应的区别,即事件响应规则,从源码View出发
新建ViewOnClickDemo演示
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<ImageView
android:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_launcher"/>
</LinearLayout>
imageView的控件对象:
//按钮点击事件
imageView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
/*
* ACTION_DOWN 0
* ACTION_UP 1
* ACTION_MOVE 2
*/
/*
* false 响应一次 action_down dispatchTouchEvent 返回false 没有响应所有的事件
* true 响应两次 按下 抬起 dispatchTouchEvent 返回true ,捕获了所有事件,消费
*/
Log.i(tag, "ImageView event = "+event.getAction());
return false;
}
});
button的控件对象:
//按钮点击事件
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(tag, "button event = "+event.getAction());
/*
* false 响应两次 按下抬起 dispatchTouchEvent 返回true 消费所有事件
* true 响应两次 按下抬起 dispatchTouchEvent 返回 true 消费所有事件
*/
return false;
}
});
结论:按钮是一个特殊的控件
*ImageView控件的响应风格
观察imageView的父类有没有dispatchTouchEvent方法,归溯到View类的源码发现存在,观察其源码
public boolean dispatchTouchEvent(MotionEvent event) {
...
//mOnTouchListener就是imageView调用setOnTouchListener传递进来的对象
//onTouch返回值结果由自己重写onTouch决定的,如果返回值为true,那当前if条件满足,直接返回true,即dispatchTouchEvent返回true
// dispatchTouchEvent返回true,响应所有事件
// 如果onTouch返回的false,顺序执行onTouchEvent(event)
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
观察结论发现在imageView里return true时会让dispatchTouchEvent也返回true,而当imageView里return false时,View中的dispatchTouchEvent方法就执行return onTouchEvent(event),那么会发生什么事情呢?
public boolean onTouchEvent(MotionEvent event) {
...
//当前控件是否可以被点击,如果可以被点击进入if,返回true,即dispatchTouchEvent(ev)返回true,响应所有事件
//不可以被点击,不进入if,直接返回false,即dispatchTouchEvent(ev)返回false,不响应所有事件
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
//点击事件的调用方式,就是回调方法
performClick();
...
return true;
}
return false;
}
发现View里的源码onTouchEvent(event)方法最后对于imageView是返回一个false,最后就是imageVIew在return false时的现象(没有响应事件)
*button控件的响应风格
同样研究button:观察button的父类有没有dispatchTouchEvent方法,归溯到View类的源码发现存在,之后的研究逻辑和ImageView一样,唯一不同就是在button的点击事件里return false 时,View里的源码onTouchEvent(event)方法最后对于button是返回一个true。
经过以上的研究,就可以明白我们ViewOnClickDemo所发生的现象了
*那么我们需求是,让imageView的点击事件可以和button的风格保持一致。
我们通过imageView.setOnclickListener来对ImageView一个对应的点击事件
设置过ImageView的点击事件后,点击的风格就和button一样了
//按钮点击事件
imageView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
/*
* ACTION_DOWN 0
* ACTION_UP 1
* ACTION_MOVE 2
*/
/*
* false 响应一次 action_down dispatchTouchEvent 返回false 没有响应所有的事件
* true 响应两次 按下 抬起 dispatchTouchEvent 返回true ,捕获了所有事件,消费
*/
Log.i(tag, "ImageView event = "+event.getAction());
return false;
}
});
//设置一个点击事件
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//添加上点击事件后,onTouch返回false 响应两次次
//action_down dispatchTouchEvent 返回false 没有响应所有的事件
}
});
*现在我们再给button也设置一下点击的响应事件OnClickListener 在它的OnTouchListener设置了的同时并返回true时,其点击的响应事件没响应
如果把button的OnTouchListener设置了的同时并返回false时,会怎样?就会调用button里OnClickListener里onClick的方法内容(原因主要是在View源码里的dispatchTouchEvent方法有线索,其内部主要是最后还是调用了一个Action_Up的方法,也就是OnClickListener里onClick的回调方法的内容)
//按钮点击事件
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(tag, "button event = "+event.getAction());
/*
* false 响应两次 按下抬起 dispatchTouchEvent 返回true 消费所有事件
* true 响应两次 按下抬起 dispatchTouchEvent 返回 true 消费所有事件
*/
return false;
}
});
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.i(tag, "button onclick.......................");
//onTouch 返回true ,点击事件不会响应
//onTouch 返回false ,点击事件会响应
}
});
//dispatchTouchEvent(ev)
ViewGroup案例的实现
项目ViewGroupClickDemo演示
在其activity_main.xml里的自定义控件放入一个button控件
在自定义控件里重写拦截点击事件的方法onInterceptTouchEvent(MotionEvent ev)通过修改其return的布尔值
在MainActivity里去测试button的点击事件和layout的点击事件
对其结论进行研究
观察其自定义控件的父类源码ViewGroup中的dispatchTouchEvent的方法,
其可以发现,当onInterceptTouchEvent(MotionEvent ev)为true时执行if里面的内容(内容一定是处理其子控件的点击事件),false就不执行
图片解释其源码的操作:
回到项目zhbj_heima47中处理问题2
让MyViewPager继承MyLazyViewPager(来解除预加载和屏蔽ViewPager的左右滑动)
并实现了其两个方法,屏蔽其点击事件
让frag_home.xml里调用MyViewPager自定义控件
在HomeFragment实现
测试:预加载和手势滑动是否屏蔽了,测试成功
回到智慧北京项目:zhbj_heima47中继续实现点击新闻中心请求网络
1、给HomeFragment中的layout_content(ViewPager对象)设置一个Listener,PagerChange,去获取当前页面对象(新闻中心),并调用新闻中心的ViewPager对象中的initData的方法去初始化数据(重点)
layout_content.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
//请求网络数据,填充UI的操作
pagerList.get(position).initData();
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
2、给NewCenterPager类(新闻中心栏目)去实现initData方法,就是在方法里请求网络,使用XUtils
@Override
public void initData() {
//请求网络数据,xutil请求网络
getData();
}
3、HMApi.java的接口用来配合网络请求,并想办法让链接地址加上Test内容,就是测试的链接地址,我们需要动态的加,看HMApi怎么做
public class HMApi {
public final static String BASE_URL = "http://192.168.1.100:8080/zhbj";
public final static String NEWS_CENTER_CATEGORIES = BASE_URL
+ "/categories.json";
public static final String PHOTOS_URL = BASE_URL + "/photos/photos_1.json";
}
技巧:碰到一个接口类时,我们可以通过ctrl+T来查看其实现类
4、学会RequestParams的几种方式去传递参数
// RequestParams requestParams = new RequestParams();
// requestParams.addBodyParameter("name", "ooxx");
// RequestParams requestParams = new RequestParams();
requestParams.ad
// List<NameValuePair> arrayList = new ArrayList<NameValuePair>();
//
// BasicNameValuePair basicNameValuePair = new BasicNameValuePair("name", "ooxx");
// BasicNameValuePair basicNameValuePair1 = new BasicNameValuePair("password", "ooxx");
// BasicNameValuePair basicNameValuePair2 = new BasicNameValuePair("id", "ooxx");
//
// arrayList.add(basicNameValuePair);
// arrayList.add(basicNameValuePair1);
// arrayList.add(basicNameValuePair2);
//
// requestParams.addBodyParameter(arrayList);
5、在httpUtils.send里的一个参数实现其参数的匿名类,就是请求返回的方法onSuccess,onFailure
private void getData() {
requestData(HttpMethod.GET, HMApi.NEWS_CENTER_CATEGORIES, null,new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
//请求成功的方法
Log.i(tag, responseInfo.result);
}
@Override
public void onFailure(HttpException error, String msg) {
//请求失败的方法
}
});
}
6、接着就是点击按钮去切换新闻中心去测试,注意测试前要在清单文件里设置请求网络的权限,json访问成功
7、使用HiJson去查看访问到的json结构
8、优化initData代码:把请求网络的代码抽取出方法来放入到父类当中:requestData,在子类中我们只要调用方法并传参就OK,这样有什么好处?以后我们需要该一次编码,只要在父类中改就OK,子类就没必要修改了,并考虑到子类很多,这样的修改效率明显高很多
public abstract class BasePager {
public Context context;
private View view;
//构建UI的方法
public abstract View initView();
//填充数据的方法
public abstract void initData();
public BasePager(Context context) {
this.context = context;
//当前的view其实就是页面的展示效果
view = initView();
}
//返回当前页面样式的方法
public View getRootView(){
return view;
}
//将请求网络的操作抽取到父类中
public void requestData(HttpMethod httpMethod,String url,
RequestParams params,RequestCallBack<String> callBack) {
// RequestParams requestParams = new RequestParams();
// requestParams.addBodyParameter("name", "ooxx");
// RequestParams requestParams = new RequestParams();
requestParams.ad
// List<NameValuePair> arrayList = new ArrayList<NameValuePair>();
//
// BasicNameValuePair basicNameValuePair = new BasicNameValuePair("name", "ooxx");
// BasicNameValuePair basicNameValuePair1 = new BasicNameValuePair("password", "ooxx");
// BasicNameValuePair basicNameValuePair2 = new BasicNameValuePair("id", "ooxx");
//
// arrayList.add(basicNameValuePair);
// arrayList.add(basicNameValuePair1);
// arrayList.add(basicNameValuePair2);
//
// requestParams.addBodyParameter(arrayList);
HttpUtils httpUtils = new HttpUtils();
httpUtils.send(httpMethod,url,params,callBack);
}
}
9、实现initData中的缓存的方法:就是为了二次访问网络时可以进行判断后决定访问缓存还是真正访问网络,存储缓存的方法:1、文件(sp)2、数据库 3、内存存储,第三种不推荐
private void getData() {
requestData(HttpMethod.GET, HMApi.NEWS_CENTER_CATEGORIES, null,new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
//请求成功的方法
Log.i(tag, responseInfo.result);
//存储操作 1,文件(sp) 2,数据库 3,内存存储
//url作为key
SharedPreferencesUtil.saveStringData(context,HMApi.NEWS_CENTER_CATEGORIES,responseInfo.result);
processData(responseInfo.result);
}
@Override
public void onFailure(HttpException error, String msg) {
//请求失败的方法
}
});
}
10、把sp写成一个工具类SharePreferenceUtils
public class SharedPreferencesUtil {
private static String CONFIG = "config";
private static SharedPreferences sharedPreferences;
//写入
public static void saveStringData(Context context,String key,String value){
if(sharedPreferences==null){
sharedPreferences = context.getSharedPreferences(CONFIG, Context.MODE_PRIVATE);
}
sharedPreferences.edit().putString(key, value).commit();
}
//读出
public static String getStringData(Context context,String key,String defValue){
if(sharedPreferences==null){
sharedPreferences = context.getSharedPreferences(CONFIG, Context.MODE_PRIVATE);
}
return sharedPreferences.getString(key, defValue);
}
}
11、在initData使用该sp的工具类去做缓存处理,在sp配置时可以使用url来存储sp的key,并把请求网络的代码再次抽取成getData方法,并在initData加判断逻辑是否二次访问
@Override
public void initData() {
//判断一下当前本地是否有数据
String result = SharedPreferencesUtil.getStringData(context, HMApi.NEWS_CENTER_CATEGORIES, "");
if(!TextUtils.isEmpty(result)){
//有值,展示,json解析过程
processData(result);
}
//请求网络数据,xutil请求网络
getData();
}
private void getData() {
requestData(HttpMethod.GET, HMApi.NEWS_CENTER_CATEGORIES, null,new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
//请求成功的方法
Log.i(tag, responseInfo.result);
//存储操作 1,文件(sp) 2,数据库 3,内存存储
//url作为key
SharedPreferencesUtil.saveStringData(context,HMApi.NEWS_CENTER_CATEGORIES,responseInfo.result);
processData(responseInfo.result);
}
@Override
public void onFailure(HttpException error, String msg) {
//请求失败的方法
}
});
}
12、在判断里和onSuccess里加入解析json的方法,请求网络的结果processData(result)
processData(responseInfo.result);
13、processData(result)的解析json逻辑:把源码复制过来,这是开源的jar文件(在资料里面提供),把json解析成相对的javaBean
//解析json的操作 private void processData(String result) { }
14、实现新闻中心的javaBean-->NewCenter,json中的数据和我们java中的集合最类似。在给json里的复杂类型再设计出一个javaBean-->NewCenterItem,注意我们在javaBean里定义的字段必须和json里的数据一模一样
public class NewCenter {
public List<NewCenterItem> data;
public List<String> extend;
public String retcode;
public class NewCenterItem{
public List<Children> children;
public String id;
public String title;
public String type;
public String url;
public String url1;
public String dayurl;
public String excurl;
public String weekurl;
}
public class Children{
public String id;
public String title;
public String type;
public String url;
}
}
15、继续回到13步里去解析json
//解析json的操作
private void processData(String result) {
//json解析
Gson gson = new Gson();
NewCenter newCenter = gson.fromJson(result, NewCenter.class);
Log.i(tag, newCenter.data.get(0).children.get(0).url);
}
测试:
(!!!一下几步涉及到在别的类如何获取Fragment的对象并传数据)
16、把json的javaBean去显示到左侧侧拉栏,在processData里进行for循环拿到数据后存到titleList集合里,该titleList需要给左侧侧拉栏条目MunuFragment对象去使用,现在问题是如何去获取MenuFragment对象?
//解析json的操作
private void processData(String result) {
//json解析
Gson gson = new Gson();
NewCenter newCenter = gson.fromJson(result, NewCenter.class);
Log.i(tag, newCenter.data.get(0).children.get(0).url);
titleList.clear();
for(int i=0;i<newCenter.data.size();i++){
titleList.add(newCenter.data.get(i).title);
}
}
17、记得我们在替换fragment时的replace方法里有一个参数是"MENU",这就是为了提供给别的类去获取,所以在MainActivity里设置一个获取MenuFragment对象的方法getMenuFragment
MainActivity.java
//fragment替换左侧侧拉栏目
MenuFragment menuFragment = new MenuFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.menu, menuFragment, "MENU")
.commit();
MenuFragment.java
public class MenuFragment extends BaseFragment {
private static final String tag = "MenuFragment";
private List<String> titleList = new ArrayList<String>();
@Override
public View initView() {
TextView textView = new TextView(context);
textView.setText("左侧侧拉栏目");
return textView;
}
@Override
public void initData() {
}
public void initMenu(List<String> titleList) {
this.titleList = titleList;
Log.i(tag, "titleList.size() = "+titleList.size());
}
}
18、回到NewCenterPager中通过一直使用的context上下文对象,其实这个就是MainActivity对象,那么就可以使用context去调用获取MenuFragment对象的方法getMenuFragment(侧拉栏对象),给该对象传参数,给该对象类创建一个初始化栏目的方法initMenu(ListtitlesList)去初始化,把已经准备好了的titleList集合传给它,并实现侧拉栏展示
//解析json的操作
private void processData(String result) {
//json解析
Gson gson = new Gson();
NewCenter newCenter = gson.fromJson(result, NewCenter.class);
Log.i(tag, newCenter.data.get(0).children.get(0).url);
titleList.clear();
for(int i=0;i<newCenter.data.size();i++){
titleList.add(newCenter.data.get(i).title);
}
//当前的titleList需要给左侧侧拉条MenuFragment对象目去使用
//MenuFragment获取其对象???---->mainactivity对象??
((MainActivity)context).getMenuFragment().initMenu(titleList);
}