菜菜Android

Java 纯代码创建安卓页面布局(含异步加载图片)

2016-10-04  本文已影响245人  缺水的海豚

开发环境

总体效果

总体效果图

说明:
本示例采用的是Fragment实现的
列表中的分割线,是采用的 LinearLayout 自带的 divider 实现的

界面结构(Layout文件)

在 Fragment 中,采用 ScrollView + LinearLayout 实现,代码如下所示:

<ScrollView
    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:scrollbars="vertical"    
    tools:context=".Fragment.HomeFrg">    
    <LinearLayout        
        android:id="@+id/frg_home"        
        android:layout_width="match_parent"        
        android:layout_height="match_parent"        
        android:orientation="vertical"        
        android:divider="@drawable/sep_home"        
        android:showDividers="middle" />
</ScrollView>

divider 的实现详见上述代码中,LinearLayout 的最后两行的属性声明。
文件目录如下:


divider 文件目录

sep_home.xml 文档结构如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/colorBG" />    
    <size android:height="10dp" />
</shape>

背景颜色定义在 values/colors 中,如下所示:

<color name="colorBG">#EEEEEE</color>

说明: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<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();

    // 固定 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<Drawable> 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>(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 来进行各处留白距离的微调。

上一篇下一篇

猜你喜欢

热点阅读