Tesis de Lic. en Ciencias de la Computación

Buenas, les paso a compartir mi trabajo final de Licenciatura en Ciencias de la Computación.
Consiste en un dispositivo para Bioacústica con Software y Hardware de Código Abierto, donde aplico un caso de estudio para “Registrar sonidos de animales en el Desierto”
Integra las dos tecnologías Arduino y Android.
Introducción del tema de tesis

Construcción del dispositivo bioacústico

Para hacer el trabajo final de tesis, me basado en tutoriales anteriores
De de este blog Curso Android Studio

De mi otro blog Curso Arduino Mega

Para tener en cuenta, depende si trabajan con el arduino uno o el arduino mega
Para DESCARGAR 

Conexión bluetooth Android con Arduino

Buenas este es el primer tutorial enlazado a mis segundo blog CursoArduinoMega.
Este tutorial consiste conectar a través de una conexión bluetooth tu dispositivo Android con el Arduino.
Vamos a enviar y recibir información.
Desde el celular Android, vamos a tener dos botones. ON OFF que van encender o apagar respectivamente
Desde el Arduino vamos a enviar 1, 2,3, 4. Ahora utilizamos estos numeros pero podría ser cualquier tipo de información como la temperatura y la humedad de un DHT11 
Todos los demás botones es puramente demostrativo que otras funcionalidades se le pueden agregar.

Fuentes: wingoodharry arduino

Configurar HC06

Comencemos con arduino

//#include "DHT.h"
//#define DHTPIN 7
//#define DHTTYPE DHT11
//DHT dht(DHTPIN, DHTTYPE);

int led = 13; //led Rojo de prueba de conexión

float voltageValue[4] = {0,0,0,0};
char inbyte = 0; //Char para leer el led

void setup() {
// initialise serial communications at 9600 bps:
Serial.begin(9600);
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
//dht.begin();
}

void loop() {
getVoltageValue();
//when serial values have been received this will be true

if (Serial.available() > 0)
{
inbyte = Serial.read();
Serial.println(inbyte);
if (inbyte == '2')
{
digitalWrite(led, LOW); //LED off
voltageValue[0] = 0;
}
if (inbyte == '1')
{
digitalWrite(led, HIGH); //LED on
voltageValue[0] = 1;
}
}
sendAndroidValues();
delay(2000);
}

void getVoltageValue()
{
voltageValue[0] = 1; //led
voltageValue[1] = 2;
voltageValue[2] = 3;
voltageValue[3] = 4;

}

//enviar los valores por el dipositivo android por el modulo Bluetooth
void sendAndroidValues()
{
Serial.print('#'); //hay que poner # para el comienzo de los datos, así Android sabe que empieza el String de datos
for(int k=0; k<4; k++)
{
Serial.print(voltageValue[k]);
Serial.print('+'); //separamos los datos con el +, así no es más fácil debuggear la información que enviamos
}
Serial.print('~'); //con esto damos a conocer la finalización del String de datos
Serial.println();
delay(10); //agregamos este delay para eliminar tramisiones faltantes
}

Ahora la conexión.
Nota: En código anterior no declaramos el TX y RX (recibir e enviar información) porque vamos a usar D0 y D1. En caso contrario deberíamos declararlo como en el siguiente ejemplo (ver)

SoftwareSerial BT(10,11); //10 RX, 11 TX.

  • TXD: Transmisión de datos.
  • RXD: Recepción de datos a un voltaje de 3,3V.
Muestro dos opciones. Ambas funcionan.
Lo más importante, cual es muy fácil de equivocarse si uno trabajo apurado, rápido.
Es que 
  • el RXD hc06 va con el TX0 del arduino
  • el TXD hc06 va con el RX0 del arduino
como muestro en las imágenes

Fotos reales

Y sin mas preámbulos a los que nos interesa el código de Android
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tatoado.ramabluewingood" >

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".DeviceListActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"></activity>
</application>

</manifest>

device_name.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:padding="5dp">

</TextView>

activy_main.xml (esto tiene que estar dentro de un linearlayout o relative o como gusten, para no ser muy largo solo puse lo esencial en este layout)

 <Button
android:id="@+id/buttonOn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LED ON" />

<Button
android:id="@+id/buttonOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LED OFF />

<TextView
android:id="@+id/sensorView0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/sensorView1
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/sensorView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/sensorView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

device_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView android:id="@+id/title_paired_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Seleccione un dispositivo btSerial "
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/paired_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stackFromBottom="false"
android:layout_weight="1"
/>

<TextView
android:id="@+id/connecting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />

<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Si no hay dispositivos en la lista, por favor enlaza tu dispositivo en la configuración de Android"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_margin="5dp"
android:textSize="18dp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">

</LinearLayout>

</LinearLayout>

MainActivity.java

       

package com.tatoado.ramabluewingood;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

Button btnOn, btnOff;
TextView txtArduino, txtString, txtStringLength, sensorView0, sensorView1, sensorView2, sensorView3;
TextView txtSendorLDR;
Handler bluetoothIn;

final int handlerState = 0; //used to identify handler message
private BluetoothAdapter btAdapter = null;
private BluetoothSocket btSocket = null;
private StringBuilder recDataString = new StringBuilder();

private ConnectedThread mConnectedThread;

// SPP UUID service - this should work for most devices
private static final UUID BTMODULEUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

// String for MAC address
private static String address = null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//Link the buttons and textViews to respective views
btnOn = (Button) findViewById(R.id.buttonOn);
btnOff = (Button) findViewById(R.id.buttonOff);
txtString = (TextView) findViewById(R.id.txtString);
txtStringLength = (TextView) findViewById(R.id.testView1);
sensorView0 = (TextView) findViewById(R.id.sensorView0);
sensorView1 = (TextView) findViewById(R.id.sensorView1);
sensorView2 = (TextView) findViewById(R.id.sensorView2);
sensorView3 = (TextView) findViewById(R.id.sensorView3);

txtSendorLDR = (TextView) findViewById(R.id.tv_sendorldr);


bluetoothIn = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == handlerState) { //if message is what we want
String readMessage = (String) msg.obj; // msg.arg1 = bytes from connect thread
recDataString.append(readMessage); //keep appending to string until ~
int endOfLineIndex = recDataString.indexOf("~"); // determine the end-of-line
if (endOfLineIndex > 0) { // make sure there data before ~
String dataInPrint = recDataString.substring(0, endOfLineIndex); // extract string
txtString.setText("Datos recibidos = " + dataInPrint);
int dataLength = dataInPrint.length(); //get length of data received
txtStringLength.setText("Tamaño del String = " + String.valueOf(dataLength));

if (recDataString.charAt(0) == '#') //if it starts with # we know it is what we are looking for
{
String sensor0 = recDataString.substring(1, 5); //get sensor value from string between indices 1-5
String sensor1 = recDataString.substring(6, 10); //same again...
String sensor2 = recDataString.substring(11, 15);
String sensor3 = recDataString.substring(16, 20);

if(sensor0.equals("1.00"))
sensorView0.setText("Encendido"); //update the textviews with sensor values
else
sensorView0.setText("Apagado"); //update the textviews with sensor values
sensorView1.setText(sensor1);
sensorView2.setText(sensor2);
sensorView3.setText(sensor3);
//sensorView3.setText(" Sensor 3 Voltage = " + sensor3 + "V");
}
recDataString.delete(0, recDataString.length()); //clear all string data
// strIncom =" ";
dataInPrint = " ";
}
}
}
};

btAdapter = BluetoothAdapter.getDefaultAdapter(); // get Bluetooth adapter
checkBTState();


// Set up onClick listeners for buttons to send 1 or 0 to turn on/off LED
btnOff.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mConnectedThread.write("2"); // Send "0" via Bluetooth
Toast.makeText(getBaseContext(), "Apagar el LED", Toast.LENGTH_SHORT).show();
}
});

btnOn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mConnectedThread.write("1"); // Send "1" via Bluetooth
Toast.makeText(getBaseContext(), "Encender el LED", Toast.LENGTH_SHORT).show();
}
});
}


private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {

return device.createRfcommSocketToServiceRecord(BTMODULEUUID);
//creates secure outgoing connecetion with BT device using UUID
}

@Override
public void onResume() {
super.onResume();

//Get MAC address from DeviceListActivity via intent
Intent intent = getIntent();

//Get the MAC address from the DeviceListActivty via EXTRA
address = intent.getStringExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS);

//create device and set the MAC address
//Log.i("ramiro", "adress : " + address);
BluetoothDevice device = btAdapter.getRemoteDevice(address);

try {
btSocket = createBluetoothSocket(device);
} catch (IOException e) {
Toast.makeText(getBaseContext(), "La creacción del Socket fallo", Toast.LENGTH_LONG).show();
}
// Establish the Bluetooth socket connection.
try
{
btSocket.connect();
} catch (IOException e) {
try
{
btSocket.close();
} catch (IOException e2)
{
//insert code to deal with this
}
}
mConnectedThread = new ConnectedThread(btSocket);
mConnectedThread.start();

//I send a character when resuming.beginning transmission to check device is connected
//If it is not an exception will be thrown in the write method and finish() will be called
mConnectedThread.write("x");
}

@Override
public void onPause()
{
super.onPause();
try
{
//Don't leave Bluetooth sockets open when leaving activity
btSocket.close();
} catch (IOException e2) {
//insert code to deal with this
}
}

//Checks that the Android device Bluetooth is available and prompts to be turned on if off
private void checkBTState() {

if(btAdapter==null) {
Toast.makeText(getBaseContext(), "El dispositivo no soporta bluetooth", Toast.LENGTH_LONG).show();
} else {
if (btAdapter.isEnabled()) {
} else {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 1);
}
}
}

//create new class for connect thread
private class ConnectedThread extends Thread {
private final InputStream mmInStream;
private final OutputStream mmOutStream;

//creation of the connect thread
public ConnectedThread(BluetoothSocket socket) {
InputStream tmpIn = null;
OutputStream tmpOut = null;

try {
//Create I/O streams for connection
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }

mmInStream = tmpIn;
mmOutStream = tmpOut;
}


public void run() {
byte[] buffer = new byte[256];
int bytes;

// Keep looping to listen for received messages
while (true) {
try {
bytes = mmInStream.read(buffer); //read bytes from input buffer
String readMessage = new String(buffer, 0, bytes);
// Send the obtained bytes to the UI Activity via handler
bluetoothIn.obtainMessage(handlerState, bytes, -1, readMessage).sendToTarget();
} catch (IOException e) {
break;
}
}
}
//write method
public void write(String input) {
byte[] msgBuffer = input.getBytes(); //converts entered String into bytes
try {
mmOutStream.write(msgBuffer); //write bytes over BT connection via outstream
} catch (IOException e) {
//if you cannot write, close the application
Toast.makeText(getBaseContext(), "La Conexión fallo", Toast.LENGTH_LONG).show();
finish();

}
}
}
}

DeviceListActivity.java

       

package com.tatoado.ramabluewingood;

import java.util.Set;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;


public class DeviceListActivity extends Activity {
// Debugging for LOGCAT
private static final String TAG = "DeviceListActivity";
private static final boolean D = true;


// declare button for launching website and textview for connection status
Button tlbutton;
TextView textView1;

// EXTRA string to send on to mainactivity
public static String EXTRA_DEVICE_ADDRESS = "device_address";

// Member fields
private BluetoothAdapter mBtAdapter;
private ArrayAdapter mPairedDevicesArrayAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.device_list);
}

@Override
public void onResume()
{
super.onResume();
//***************
checkBTState();

textView1 = (TextView) findViewById(R.id.connecting);
textView1.setTextSize(40);
textView1.setText(" ");

// Initialize array adapter for paired devices
mPairedDevicesArrayAdapter = new ArrayAdapter(this, R.layout.device_name);

// Find and set up the ListView for paired devices
ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mDeviceClickListener);

// Get the local Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();

// Get a set of currently paired devices and append to 'pairedDevices'
Set pairedDevices = mBtAdapter.getBondedDevices();

// Add previosuly paired devices to the array
if (pairedDevices.size() > 0) {
findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);//make title viewable
for (BluetoothDevice device : pairedDevices) {
mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
} else {
String noDevices = "Ningun dispositivo pudo ser emparejado";
mPairedDevicesArrayAdapter.add(noDevices);
}
}

// Set up on-click listener for the list (nicked this - unsure)
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView av, View v, int arg2, long arg3) {

textView1.setText("Conectando...");
// Get the device MAC address, which is the last 17 chars in the View
String info = ((TextView) v).getText().toString();
String address = info.substring(info.length() - 17);

// Make an intent to start next activity while taking an extra which is the MAC address.
Intent i = new Intent(DeviceListActivity.this, MainActivity.class);
i.putExtra(EXTRA_DEVICE_ADDRESS, address);
startActivity(i);
}
};

private void checkBTState() {
// Check device has Bluetooth and that it is turned on
mBtAdapter=BluetoothAdapter.getDefaultAdapter(); // CHECK THIS OUT THAT IT WORKS!!!
if(mBtAdapter==null) {
Toast.makeText(getBaseContext(), "El dispositivo no soporta Bluetooth", Toast.LENGTH_SHORT).show();
} else {
if (mBtAdapter.isEnabled()) {
Log.d(TAG, "...Bluetooth Activado...");
} else {
//Prompt user to turn on Bluetooth
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 1);

}
}
}
}

AlertDialog llama WebView

Nuestro es objetivo es apretar un botón y nos aparezca esta pantalla.
La pregunta que nos podríamos de hacer antes de seguir, para que usar un alertDialog y no hacer una simple llamada

startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("www.google.com.ar")));

La respuesta es simple, porque con la línea de código anterior, SALIMOS de nuestra aplicación para entrar al navegador (chrome) y entrar a www.google.com.ar (o cualquier otra url que nosotros pongamos).
Y eso es un problema, si el usuario sale de nuestra app, es muy posible que no vuelva, porque a lo mejor ya se engancha con otra cosa y después otra y se fue.
La idea siempre es que este en la app y nunca salga, a menos que intencionalmente quiera hacerlo.

Entonces, para ello vamos a llamar “navegador” pero sin salirnos de nuestro app. Esto es muy parecido a lo que hace Fabook, cuando clickeamos en las fotos, llama a un alert vemos las foto, y cuando apretamos escape, volvemos a donde estabamos.

Lo que tenemos que hacer llamar a esta función

webViewResultados(); 

Código:

private void webViewResultados() {
AlertDialog.Builder alert = new AlertDialog.Builder(
getApplicationContext());
//setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

//alert.setTitle("Resultados Salomon k21");

WebView wv = new WebView(getApplicationContext());
wv.getSettings().setJavaScriptEnabled(true);
wv.loadUrl("https://docs.google.com/spreadsheets/d/1VvjXWpiQWvxH7iD9E4g0dq0Fm-JeV_Sm1y-_afDAb5Q/pubhtml");
wv.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view,
String url) {
view.loadUrl(url);

return true;
}
});

alert.setNegativeButton("Close",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
}
});
Dialog d = alert.setView(wv).create();
d.show();
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(d.getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.FILL_PARENT;
lp.height = WindowManager.LayoutParams.FILL_PARENT;
d.getWindow().setAttributes(lp);
}

Si tienen problemas con el redimensionamiento verticual cuando rotan la pantalla pueden comentar

//                lp.height = WindowManager.LayoutParams.FILL_PARENT;

Ejemplo completo
Prueba.java

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.WindowManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;

public class Prueba extends ActionBarActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

this.setContentView(R.layout.prueba);
//setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

Button btnPrueba = (Button) findViewById(R.id.buttonPrueba);
btnPrueba.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder alert = new AlertDialog.Builder(
Prueba.this);
//setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
//alert.setTitle("Resultados Salomon k21");

WebView wv = new WebView(Prueba.this);
wv.getSettings().setJavaScriptEnabled(true);
wv.loadUrl("https://docs.google.com/spreadsheets/d/1VvjXWpiQWvxH7iD9E4g0dq0Fm-JeV_Sm1y-_afDAb5Q/pubhtml");
wv.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view,
String url) {
view.loadUrl(url);

return true;
}
});

alert.setNegativeButton("Close",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
}
});
Dialog d = alert.setView(wv).create();
d.show();
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(d.getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.FILL_PARENT;
// lp.height = WindowManager.LayoutParams.FILL_PARENT;
d.getWindow().setAttributes(lp);
}
});

//WebView myWebView = (WebView) findViewById(R.id.webView);
//myWebView.loadUrl("https://amatellanes.wordpress.com/");

}
}

prueba.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Button"
android:id="@+id/buttonPrueba" />
</LinearLayout>

No se olviden de habilitar conexiones de internet en el AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

Nota: Si les sale el error

android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application

cambien el

getApplicationContext()

por el nombre de la clase.this por ejemplo

Prueba.this

ListView con filas personalizadas

En este tutorial voy a enseñar como hacer un listView personalizado como podrán haber visto en Proyecto turismo (ver). Ahí también pueden encontrar el código, pero acá solo explicaré este detalle, que es menor.
Recordemos que todo entra por lo ojos, y que tenga un buen aspecto nuestra app es fundamental.

Recomiedo ver -> Animación entre activitiesMultilenguajeActionBar ir atrás (que son los que nombre en el videotutorial)

La idea es pasar de ListView como estos ->

A ListView personalizados como estos ->

Realmente hay mucha diferencia, parece un boton 3D.
Vuelvo a repetir, todo entra por los ojos.
Empezemos…

Para ello necesitamos agregar en la carpeta “drawable”

post_border_style.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_pressed="true"><shape>
<solid android:color="#cccccc" />
</shape></item>
<item><shape>
<gradient android:angle="270" android:endColor="#ccc" android:startColor="#fff" />
<corners android:radius="2dp" />
</shape></item>

</selector>

post_border_background.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_pressed="true"><shape>
<solid android:color="#cccccc" />
</shape></item>
<item><shape>
<gradient android:angle="90" android:endColor="#f5f5f5" android:startColor="#fff" />
</shape></item>

</selector>

Una vez ya agregado lo anterior pasamos a la carpeta “layout” agregamos el siguiente xml

list_row.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="wrap_content"
android:background="#f0f0f0"
android:orientation="vertical" >

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/post_border_style"
android:orientation="vertical" >

<LinearLayout
android:id="@+id/box_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:background="@drawable/post_background_style"
android:orientation="horizontal" >

<LinearLayout
android:id="@+id/single_post_circuito_linearbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:orientation="horizontal"
android:padding="5dp" >

<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.3">

<ImageView
android:layout_width="match_parent"
android:layout_height="80dp"
android:id="@+id/imagen_single_post_circuito"
android:src="@drawable/circuitochico_plaza"
android:scaleType="centerCrop" />
</LinearLayout>

<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.7">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Plaza 25 de Mayo"
android:id="@+id/tv_titulo_single_post_circuito"
android:layout_marginLeft="5dp"
android:textColor="#ff000000" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Es la plaza principal de la ciudad de San Juan y es donde se encuentra el kilómetro 0 de la provincia."
android:id="@+id/tv_contenido_single_post_circuito"
android:layout_marginLeft="5dp"
android:textColor="#ff868686" />

</LinearLayout>

</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>

Ya terminamos con los archivos xml
Pasamos a la carpeta de java, utilizaremos el ListViewAdapter aprendido en ListView con Imagenes (ver)

ListViewAdapter
public class ListViewAdapter extends BaseAdapter {
// Declare Variables
Context context;
int[] imagenes;
String[] titulos;
String[] contenido;
LayoutInflater inflater;

public ListViewAdapter(Context context, int[] imagenes, String[] titulos, String[] contenido ) {
this.context = context;
this.imagenes = imagenes;
this.titulos = titulos;
this.contenido = contenido;
}

@Override
public int getCount() {
return titulos.length;
}

@Override
public Object getItem(int position) {
return null;
}

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

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

// Declare Variables
ImageView imgImg;
TextView txtTitle;
TextView txtContenido;

//http://developer.android.com/intl/es/reference/android/view/LayoutInflater.html
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View itemView = inflater.inflate(R.layout.single_post_circuito, parent, false);

// Locate the TextViews in listview_item.xml
imgImg = (ImageView) itemView.findViewById(R.id.imagen_single_post_circuito);
txtTitle = (TextView) itemView.findViewById(R.id.tv_titulo_single_post_circuito);
txtContenido = (TextView) itemView.findViewById(R.id.tv_contenido_single_post_circuito);

// Capture position and set to the TextViews
imgImg.setImageResource(imagenes[position]);
txtTitle.setText(titulos[position]);
txtContenido.setText(contenido[position]);

return itemView;
}
}

android.R error

El problema de android.R, es un error que te genera mucha impotencia porque estas trabajando tranquilamente y de repente este problema que sale de la nada.
Os aquí les ensañaré como solucionarlo

Primero que nada, para que sirve la clase R.
Es ella la que utilizamos para usar los drawable, los mipmaps, color, intenger, string, etc.

Fuente: Stackoverflow

  1. Agregar la librería “import android.R; “
  2. Agregar la librería “import android.R.*; “
  3. Ir a la pestaña Build -> Clean Project
  4. Ir a la pestaña Build -> Rebuild Project
  5. Cerrar el Android Studio y volver a abrirlo.