开发环境
- macOS Sierra (10.12)
- Android Studio 2.2 (minSDK 5.1)
总体效果
说明:
本示例采用的是Fragment实现的
列表中的分割线,是采用的 LinearLayout 自带的 divider 实现的
界面结构(Layout文件)
在 Fragment 中,采用 ScrollView + LinearLayout 实现,代码如下所示:
divider 的实现详见上述代码中,LinearLayout 的最后两行的属性声明。
文件目录如下:
sep_home.xml 文档结构如下:
背景颜色定义在 values/colors 中,如下所示:
#EEEEEE
说明:divider 采用 Drawable 的 shape 来实现,在 shape 中,一定要添加 solid 和 size 两个元素,即使是透明背景色,也要添加这两个元素。并且,颜色的设置也要明文声明。
至此,界面布局的 xml 文件就准备完毕了,接下来开始些功能实现。
代码结构(Java 文件)
关于加载图片,在现在(新版本)的安卓开发过程中,如果在主线程直接加载网络图片,会报 NetworkOnMainThreadException 异常。
所以,这里采用了异步(结合线程池)加载的方式来进行。
当然,也可以采用成熟的第三方组件,如:Picasso(主页、Github) 等。
本文自行实现,先创建一个自定义的图像类,如下所示:
import android.graphics.drawable.Drawable;
import android.os.Handler;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestImage {
// 为了加快速度,在内存中开启缓存
// 主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动
public Map> imageCache = new HashMap>();
// 固定 10 个线程来执行任务
private ExecutorService _exeService = Executors.newFixedThreadPool(10);
private final Handler _handler = new Handler();
public Drawable getImage(final String url, final Callback callback) {
// 缓存中存在就用缓存中的图片
if (imageCache.containsKey(url)) {
SoftReference softReference = imageCache.get(url);
if (softReference.get() != null) {
return softReference.get();
}
}
// 缓存中没有图片,就从网络中获取图片,同时,存入缓存
_exeService.submit(new Runnable() {
@Override
public void run() {
final Drawable drawable = getImage(url);
imageCache.put(url, new SoftReference(drawable));
_handler.post(new Runnable() {
@Override
public void run() {
callback.imageLoaded(drawable);
}
});
}
});
return null;
}
// 从网络中获取图片
protected Drawable getImage(String url) {
Drawable drawable = null;
try {
drawable = Drawable.createFromStream(new URL(url).openStream(), "img.png");
} catch (Exception e) {
e.printStackTrace();
}
return drawable;
}
// 回调接口
public interface Callback {
void imageLoaded(Drawable drawable);
}
}
特别说明:用 final 参数的原因是:防止方法参数在调用时被篡改。
图像类建立好了之后,就可以在主类中调用了。
接下来,就是在主程序中调用了。
同时,在主程序(Fragment)中,采用动态创建各元素的方式来进行布局,全部代码如下:
public class HomeFrg extends Fragment {
private LinearLayout _layout;
//private TestImage _testImage = new TestImage();
public HomeFrg() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frg_home, container, false);
initView(view);
// Inflate the layout for this fragment
return view;
}
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
if (getView() != null) {
getView().setVisibility(menuVisible ? View.VISIBLE : View.GONE);
}
}
private void initView(View view) {
_layout = (LinearLayout) view.findViewById(R.id.frg_home);
// TODO:从数据库获取数据
// 这里直接循环 3 次来进行界面效果的展示
for (int i = 0; i < 3; i++) {
initCell(view);
}
}
private void initCell(View view) {
Context self = this.getContext();
// 创建单元格(RelativeLayout)
RelativeLayout.LayoutParams layoutWrapper = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
RelativeLayout wrapper = new RelativeLayout(self);
wrapper.setBackgroundColor(Helper.getColor(self, R.color.colorWhite));
wrapper.setPadding(0, 30, 0, 30);
_layout.addView(wrapper, layoutWrapper);
// 创建封面图片(ImageView)
RelativeLayout.LayoutParams layoutCover = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 600);
ImageView imgCover = new ImageView(self);
int idCover = view.generateViewId(); // 为了依次分开排列各控件,需设定控件的 Id,这里用 view.generateViewId() 自动产生
imgCover.setId(idCover);
loadImage("http://pic9.nipic.com/20100904/4845745_195609329636_2.jpg", imgCover); // 异步加载任意网络图片(用于测试)
imgCover.setScaleType(ImageView.ScaleType.CENTER_CROP);
imgCover.setPadding(20, 0, 20, 0);
wrapper.addView(imgCover, layoutCover);
// 创建标题(TextView)
RelativeLayout.LayoutParams layoutTitle = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutTitle.setMargins(20, 0, 20, 0);
layoutTitle.addRule(RelativeLayout.BELOW, idCover); // 在上一个元素的下方进行呈现,否则,在界面中,两个元素会重叠在一起
TextView txtTitle = new TextView(self);
int idTitle = view.generateViewId();
txtTitle.setId(idTitle);
txtTitle.setText("标题内容标题内容标题内容标题内容标题内容标题内容");
txtTitle.setTextSize(20);
txtTitle.setEllipsize(TextUtils.TruncateAt.END); // 末尾多余字符用省略号代替
txtTitle.setSingleLine(); // 设置单行显示
txtTitle.setTextColor(Helper.getColor(self, R.color.colorBlack));
wrapper.addView(txtTitle, layoutTitle);
// 创建作者(TextView)
RelativeLayout.LayoutParams layoutAuthor = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutAuthor.setMargins(20, 0, 20, 0);
layoutAuthor.addRule(RelativeLayout.BELOW, idTitle);
TextView txtAuthor = new TextView(self);
int idAuthor = view.generateViewId();
txtAuthor.setId(idAuthor);
txtAuthor.setText("作者名称");
txtAuthor.setTextColor(Helper.getColor(self, R.color.colorBlack));
wrapper.addView(txtAuthor, layoutAuthor);
// 创建日期(TextView)
RelativeLayout.LayoutParams layoutTime = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutTime.setMargins(20, 0, 20, 0);
layoutTime.addRule(RelativeLayout.BELOW, idAuthor);
TextView txtTime = new TextView(self);
txtTime.setText("2016年9月22日 16:33");
wrapper.addView(txtTime, layoutTime);
}
// 再次封装 TestImage 类,方便本页面进行调用
private void loadImage(String url, final ImageView imageView) {
Drawable imgCache = new TestImage().getImage(url, new TestImage.Callback() {
@Override
public void imageLoaded(Drawable drawable) {
imageView.setImageDrawable(drawable);
}
});
if (imgCache != null) {
imageView.setImageDrawable(imgCache);
}
}
}
至此,所有 Java 代码实现完毕。
写在最后
在 RelativeLayout 的代码布局中,可通过 LayoutParams 的 setMargins 和控件自身的 setPadding 来进行各处留白距离的微调。